commit c2453d643472ddc61c63aeb53322b31d0beae9b4 Author: wangchunxiang Date: Fri Mar 27 17:36:48 2026 +0800 chore(project): 添加项目配置文件和忽略规则 - 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件 diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..19012cb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,38 @@ +--- +name: Bug Report +about: If you would like to report a issue to Seata, please use this template. + + +--- + +- [ ] I have searched the [issues](https://github.com/seata/seata/issues) of this repository and believe that this is not a duplicate. + +### Ⅰ. Issue Description + + +### Ⅱ. Describe what happened + + If there is an exception, please attach the exception trace: + +``` +Just paste your stack trace here! +``` + + +### Ⅲ. Describe what you expected to happen + + +### Ⅳ. How to reproduce it (as minimally and precisely as possible) + +1. xxx +2. xxx +3. xxx + +### Ⅴ. Anything else we need to know? + + +### Ⅵ. Environment: + +- JDK version : +- OS : +- Others: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000..b386516 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,16 @@ +--- +name: Feature Request +about: Suggest an idea for Seata + +--- + +## Why you need it? + Is your feature request related to a problem? Please describe in details + + +## How it could be? +A clear and concise description of what you want to happen. You can explain more about input of the feature, and output of it. + + +## Other related information +Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d773ccb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ + + +### Ⅰ. Describe what this PR did + + +### Ⅱ. Does this pull request fix one issue? + + + +### Ⅲ. Why don't you add test cases (unit test/integration test)? + + +### Ⅳ. Describe how to verify it + + +### Ⅴ. Special notes for reviews + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1e8139d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: build + +on: + push: + branches: [ develop,master ] + pull_request: + branches: [ develop,master ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + java: [8, 11] + os: [ ubuntu-18.04 ] + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + - name: Set up ENV + # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + run: if [ "${{ matrix.java }}" == "8" ]; then + echo "IMAGE_NAME=openjdk:8u212-jre-alpine" >> $GITHUB_ENV; + elif [ "${{ matrix.java }}" == "11" ]; then + echo "IMAGE_NAME=openjdk:11-jre-stretch" >> $GITHUB_ENV; + fi + - name: Build with Maven + env: + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + # https://docs.github.com/cn/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context + run: if [ "${{github.event_name}}" == "push" ] && [ "${{github.ref}}" == "refs/heads/develop" ]; then + ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Dlicense.skip=false -P image -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + else + ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Dlicense.skip=false -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + fi + - name: Codecov + uses: codecov/codecov-action@v1 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afb8e49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# maven ignore +target/ +*.jar +*.war +*.zip +*.tar +*.tar.gz +*.class +.flattened-pom.xml +dependency-reduced-pom.xml + +# eclipse ignore +.settings/ +.project +.classpath + +# idea ignore +.idea/ +*.ipr +*.iml +*.iws + +# temp ignore +*.log +*.cache +*.diff +*.patch +*.tmp +/distribution/bin/ +/distribution/conf/ +/distribution/lib/ +/distribution/logs/ +/distribution/*/bin/ +/distribution/*/conf/ +/distribution/*/lib/ +/distribution/*/logs/ +/server/*root.* +/server/.root.* +/server/sessionStore/ +/server/db_store/ +/sessionStore/ +/test/sessionStore/ +/distribution/sessionStore/ +/distribution/*/sessionStore/ +/file_store/ + +# system ignore +.DS_Store +Thumbs.db +*.orig + +#h2 +*.db diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..a3f9f18 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4bf0b97 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: java +sudo: false # faster builds + +jdk: + - openjdk11 + - openjdk8 + +cache: + directories: + - $HOME/.m2 + +install: true + +before_script: + - if [ "$TRAVIS_JDK_VERSION" == "openjdk8" ]; then + export IMAGE_NAME="openjdk:8u212-jre-alpine"; + fi + - if [ "$TRAVIS_JDK_VERSION" == "openjdk11" ]; then + export IMAGE_NAME="openjdk:11-jre-stretch"; + fi + +script: + - if [ "$TRAVIS_BRANCH" == "develop" ] && [ "$TRAVIS_PULL_REQUEST" == false ]; then + travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Dlicense.skip=false -P image -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + else + travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Dlicense.skip=false -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + fi +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..832a91c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## 0.1.0 (Jan. 9, 2019) + +#### FEATURES: + +* support standalone seata-server. +* support mysql automatic transaction. +* support @GlobalTransactional spring annotation. +* support dubbo on filter. +* support mybatis ORM framework. +* support api&template. +* support dubbo,springcloud,motan ect. \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..fb337ba --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at dev-seata@googlegroups.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c018960 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,196 @@ +# Contributing to Seata + +It is warmly welcomed if you have interest to hack on Seata. First, we encourage this kind of willing very much. And here is a list of contributing guide for you. + +## Topics + +* [Reporting security issues](#reporting-security-issues) +* [Reporting general issues](#reporting-general-issues) +* [Code and doc contribution](#code-and-doc-contribution) +* [Test case contribution](#test-case-contribution) +* [Engage to help anything](#engage-to-help-anything) +* [Code Style](#code-style) + +## Reporting security issues + +Security issues are always treated seriously. As our usual principle, we discourage anyone to spread security issues. If you find a security issue of Seata, please do not discuss it in public and even do not open a public issue. Instead we encourage you to send us a private email to [dev-seata@googlegroups.com](mailto:dev-seata@googlegroups.com) to report this. + +## Reporting general issues + +To be honest, we regard every user of Seata as a very kind contributor. After experiencing Seata, you may have some feedback for the project. Then feel free to open an issue via [NEW ISSUE](https://github.com/seata/seata/issues/new/choose). + +Since we collaborate project Seata in a distributed way, we appreciate **WELL-WRITTEN**, **DETAILED**, **EXPLICIT** issue reports. To make the communication more efficient, we wish everyone could search if your issue is an existing one in the searching list. If you find it existing, please add your details in comments under the existing issue instead of opening a brand new one. + +To make the issue details as standard as possible, we setup an [ISSUE TEMPLATE](./.github/ISSUE_TEMPLATE) for issue reporters. Please **BE SURE** to follow the instructions to fill fields in template. + +There are a lot of cases when you could open an issue: + +* bug report +* feature request +* performance issues +* feature proposal +* feature design +* help wanted +* doc incomplete +* test improvement +* any questions on project +* and so on + +Also we must remind that when filling a new issue, please remember to remove the sensitive data from your post. Sensitive data could be password, secret key, network locations, private business data and so on. + +## Code and doc contribution + +Every action to make project Seata better is encouraged. On GitHub, every improvement for Seata could be via a PR (short for pull request). + +* If you find a typo, try to fix it! +* If you find a bug, try to fix it! +* If you find some redundant codes, try to remove them! +* If you find some test cases missing, try to add them! +* If you could enhance a feature, please **DO NOT** hesitate! +* If you find code implicit, try to add comments to make it clear! +* If you find code ugly, try to refactor that! +* If you can help to improve documents, it could not be better! +* If you find document incorrect, just do it and fix that! +* ... + +Actually it is impossible to list them completely. Just remember one principle: + +> WE ARE LOOKING FORWARD TO ANY PR FROM YOU. + +Since you are ready to improve Seata with a PR, we suggest you could take a look at the PR rules here. + +* [Workspace Preparation](#workspace-preparation) +* [Branch Definition](#branch-definition) +* [Commit Rules](#commit-rules) +* [PR Description](#pr-description) + +### Workspace Preparation + +To put forward a PR, we assume you have registered a GitHub ID. Then you could finish the preparation in the following steps: + +1. **FORK** Seata to your repository. To make this work, you just need to click the button Fork in right-left of [seata/seata](https://github.com/seata/seata) main page. Then you will end up with your repository in `https://github.com//seata`, in which `your-username` is your GitHub username. + +1. **CLONE** your own repository to develop locally. Use `git clone git@github.com:/seata.git` to clone repository to your local machine. Then you can create new branches to finish the change you wish to make. + +1. **Set Remote** upstream to be `git@github.com:seata/seata.git` using the following two commands: + +``` +git remote add upstream git@github.com:seata/seata.git +git remote set-url --push upstream no-pushing +``` + +With this remote setting, you can check your git remote configuration like this: + +``` +$ git remote -v +origin git@github.com:/seata.git (fetch) +origin git@github.com:/seata.git (push) +upstream git@github.com:seata/seata.git (fetch) +upstream no-pushing (push) +``` + +Adding this, we can easily synchronize local branches with upstream branches. + +### Branch Definition + +Right now we assume every contribution via pull request is for [branch develop](https://github.com/seata/seata/tree/develop) in Seata. Before contributing, be aware of branch definition would help a lot. + +As a contributor, keep in mind again that every contribution via pull request is for branch develop. While in project Seata, there are several other branches, we generally call them release branches(such as 0.6.0,0.6.1), feature branches, hotfix branches and master branch. + +When officially releasing a version, there will be a release branch and named with the version number. + +After the release, we will merge the commit of the release branch into the master branch. + +When we find that there is a bug in a certain version, we will decide to fix it in a later version or fix it in a specific hotfix version. When we decide to fix the hotfix version, we will checkout the hotfix branch based on the corresponding release branch, perform code repair and verification, and merge it into the develop branch and the master branch. + +For larger features, we will pull out the feature branch for development and verification. + + +### Commit Rules + +Actually in Seata, we take two rules serious when committing: + +* [Commit Message](#commit-message) +* [Commit Content](#commit-content) + +#### Commit Message + +Commit message could help reviewers better understand what is the purpose of submitted PR. It could help accelerate the code review procedure as well. We encourage contributors to use **EXPLICIT** commit message rather than ambiguous message. In general, we advocate the following commit message type: + +* docs: xxxx. For example, "docs: add docs about Seata cluster installation". +* feature: xxxx.For example, "feature: support oracle in AT mode". +* bugfix: xxxx. For example, "bugfix: fix panic when input nil parameter". +* refactor: xxxx. For example, "refactor: simplify to make codes more readable". +* test: xxx. For example, "test: add unit test case for func InsertIntoArray". +* other readable and explicit expression ways. + +On the other side, we discourage contributors from committing message like the following ways: + +* ~~fix bug~~ +* ~~update~~ +* ~~add doc~~ + +If you get lost, please see [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) for a start. + +#### Commit Content + +Commit content represents all content changes included in one commit. We had better include things in one single commit which could support reviewer's complete review without any other commits' help. In another word, contents in one single commit can pass the CI to avoid code mess. In brief, there are three minor rules for us to keep in mind: + +* avoid very large change in a commit; +* complete and reviewable for each commit. +* check git config(`user.name`, `user.email`) when committing to ensure that it is associated with your github ID. +* when submitting pr, please add a brief description of the current changes to the X.X.X.md file under the 'changes/' folder + + +In addition, in the code change part, we suggest that all contributors should read the [code style of Seata](#code-style). + +No matter commit message, or commit content, we do take more emphasis on code review. + + +### PR Description + +PR is the only way to make change to Seata project files. To help reviewers better get your purpose, PR description could not be too detailed. We encourage contributors to follow the [PR template](./.github/PULL_REQUEST_TEMPLATE.md) to finish the pull request. + +## Test case contribution + +Any test case would be welcomed. Currently, Seata function test cases are high priority. + +* For unit test, you need to create a test file named `xxxTest.java` in the test directory of the same module. Recommend you to use the junit5 UT framework + +* For integration test, you can put the integration test in the test directory or the seata-test module. It is recommended to use the mockito test framework. + +## Engage to help anything + +We choose GitHub as the primary place for Seata to collaborate. So the latest updates of Seata are always here. Although contributions via PR is an explicit way to help, we still call for any other ways. + +* reply to other's issues if you could; +* help solve other user's problems; +* help review other's PR design; +* help review other's codes in PR; +* discuss about Seata to make things clearer; +* advocate Seata technology beyond GitHub; +* write blogs on Seata and so on. + + +## Code Style + +Seata code style Comply with Alibaba Java Coding Guidelines. + + +### Guidelines +[Alibaba-Java-Coding-Guidelines](https://alibaba.github.io/Alibaba-Java-Coding-Guidelines/) + + +### IDE Plugin Install(not necessary) + +*It is not necessary to install, if you want to find a problem when you are coding.* + + +#### idea IDE +[p3c-idea-plugin-install](https://github.com/alibaba/p3c/blob/master/idea-plugin/README.md) + +#### eclipse IDE +[p3c-eclipse-plugin-install](https://github.com/alibaba/p3c/blob/master/eclipse-plugin/README.md) + + +In a word, **ANY HELP IS CONTRIBUTION.** \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7f77f44 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (properties) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e684f10 --- /dev/null +++ b/README.md @@ -0,0 +1,294 @@ + + +# Seata: Simple Extensible Autonomous Transaction Architecture + +[![Build Status](https://github.com/seata/seata/workflows/build/badge.svg?branch=develop)](https://github.com/seata/seata/actions) +[![codecov](https://codecov.io/gh/seata/seata/branch/develop/graph/badge.svg)](https://codecov.io/gh/seata/seata) +[![license](https://img.shields.io/github/license/seata/seata.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![maven](https://img.shields.io/maven-central/v/io.seata/seata-parent.svg)](https://search.maven.org/search?q=io.seata) +[![](https://img.shields.io/twitter/follow/seataio.svg?label=Follow&style=social&logoWidth=0)](https://twitter.com/intent/follow?screen_name=seataio) + + +## What is Seata? + +A **distributed transaction solution** with high performance and ease of use for **microservices** architecture. +### Distributed Transaction Problem in Microservices + +Let's imagine a traditional monolithic application. Its business is built up with 3 modules. They use a single local data source. + +Naturally, data consistency will be guaranteed by the local transaction. + +![Monolithic App](https://cdn.nlark.com/lark/0/2018/png/18862/1545296770244-4cedf37e-9dc6-4fc0-a97f-f4240b9d8640.png) + +Things have changed in a microservices architecture. The 3 modules mentioned above are designed to be 3 services on top of 3 different data sources ([Pattern: Database per service](http://microservices.io/patterns/data/database-per-service.html)). Data consistency within every single service is naturally guaranteed by the local transaction. + +**But how about the whole business logic scope?** + +![Microservices Problem](https://cdn.nlark.com/lark/0/2018/png/18862/1545296781231-4029da9c-8803-43a4-ac2f-6c8b1e2ea448.png) + +### How Seata do? + +Seata is just a solution to the problem mentioned above. + +![Seata solution](https://cdn.nlark.com/lark/0/2018/png/18862/1545296791074-3bce7bce-025e-45c3-9386-7b95135dade8.png) + +Firstly, how to define a **Distributed Transaction**? + +We say, a **Distributed Transaction** is a **Global Transaction** which is made up with a batch of **Branch Transaction**, and normally **Branch Transaction** is just **Local Transaction**. + +![Global & Branch](https://cdn.nlark.com/lark/0/2018/png/18862/1545015454979-a18e16f6-ed41-44f1-9c7a-bd82c4d5ff99.png) + +There are 3 basic components in Seata: + +- **Transaction Coordinator(TC):** Maintain status of global and branch transactions, drive the global commit or rollback. +- **Transaction Manager(TM):** Define the scope of global transaction: begin a global transaction, commit or rollback a global transaction. +- **Resource Manager(RM):** Manage resources that branch transactions working on, talk to TC for registering branch transactions and reporting status of branch transactions, and drive the branch transaction commit or rollback. + +![Model](https://cdn.nlark.com/lark/0/2018/png/18862/1545013915286-4a90f0df-5fda-41e1-91e0-2aa3d331c035.png) + +A typical lifecycle of Seata managed distributed transaction: + +1. TM asks TC to begin a new global transaction. TC generates an XID representing the global transaction. +2. XID is propagated through microservices' invoke chain. +3. RM registers local transaction as a branch of the corresponding global transaction of XID to TC. +4. TM asks TC for committing or rollbacking the corresponding global transaction of XID. +5. TC drives all branch transactions under the corresponding global transaction of XID to finish branch committing or rollbacking. + +![Typical Process](https://cdn.nlark.com/lark/0/2018/png/18862/1545296917881-26fabeb9-71fa-4f3e-8a7a-fc317d3389f4.png) + +For more details about principle and design, please go to [Seata wiki page](https://github.com/seata/seata/wiki). + +### History + +##### Ant Financial + +- **XTS**: Extended Transaction Service. Ant Financial middleware team developed the distributed transaction middleware since 2007, which is widely used in Ant Financial and solves the problems of data consistency across databases and services. + +- **DTX**: Distributed Transaction Extended. Since 2013, XTS has been published on the Ant Financial Cloud, with the name of DTX . + +##### Alibaba + +- **TXC**: Taobao Transaction Constructor. Alibaba middleware team started this project since 2014 to meet the distributed transaction problems caused by application architecture change from monolithic to microservices. +- **GTS**: Global Transaction Service. TXC as an Aliyun middleware product with new name GTS was published since 2016. +- **Fescar**: we started the open source project Fescar based on TXC/GTS since 2019 to work closely with the community in the future. + + +##### Seata Community + +- **Seata** :Simple Extensible Autonomous Transaction Architecture. Ant Financial joins Fescar, which make it to be a more neutral and open community for distributed transaction, and Fescar be renamed to Seata. + + + +## Maven dependency +```xml +1.4.2 + + + io.seata + seata-all + ${seata.version} + + +``` +## Quick Start + +[Quick Start](https://github.com/seata/seata/wiki/Quick-Start) + +## Documentation + + +You can view the full documentation from the wiki: [Seata wiki page](https://github.com/seata/seata/wiki). + +## Reporting bugs + +Please follow the [template](https://github.com/seata/seata/blob/develop/.github/ISSUE_TEMPLATE/BUG_REPORT.md) for reporting any issues. + + +## Contributing + +Contributors are welcomed to join the Seata project. Please check [CONTRIBUTING](./CONTRIBUTING.md) about how to contribute to this project. + + +## Contact + +* [Twitter](https://twitter.com/seataio): Follow along for latest Seata news on Twitter. + +* Mailing list: + * dev-seata@googlegroups.com , for dev/user discussion. [subscribe](mailto:dev-seata+subscribe@googlegroups.com), [unsubscribe](mailto:dev-seata+unsubscribe@googlegroups.com), [archive](https://groups.google.com/forum/#!forum/dev-seata) + + + + + +## Seata ecosystem + +* [Seata Ecosystem Entry](https://github.com/seata) - A GitHub group `seata` to gather all Seata relevant projects +* [Seata GoLang](https://github.com/opentrx/seata-golang) - Seata GoLang client and server +* [Seata Samples](https://github.com/seata/seata-samples) - Samples for Seata +* [Seata Docker](https://github.com/seata/seata-docker) - Seata integration with docker +* [Seata K8s](https://github.com/seata/seata-k8s) - Seata integration with k8s +* [Awesome Seata](https://github.com/seata/awesome-seata) - Seata's slides and video address in meetup +* [Seata Website](https://github.com/seata/seata.github.io) - Seata official website + +## Contributors + +This project exists thanks to all the people who contribute. [[Contributors](https://github.com/seata/seata/graphs/contributors)]. + +## License + +Seata is under the Apache 2.0 license. See the [LICENSE](https://github.com/seata/seata/blob/master/LICENSE) file for details. + +## Who is using + +These are only part of the companies using Seata, for reference only. If you are using Seata, please [add your company +here](https://github.com/seata/seata/issues/1246) to tell us your scenario to make Seata better. + +
+ Alibaba Group + 蚂蚁金服 + 阿里云 + 中航信 + 联通(浙江) + 中国铁塔 + 滴滴 + 中国邮政 + 58集团 + 南航 + TCL + 韵达快递 + 科大讯飞 + 奇虎360 + 收钱吧 + 太极计算机 + 美的集团 + 中国网安 + 政采云 + 浙江公安厅 + 特步 + 中通快递 + 欧莱雅百库 + 浙江烟草 + 波司登 + 凯京科技 + 点购集团 + 求是创新健康 + 科蓝 + 康美药业 + 雁联 + 学两手 + 衣二三 + 北京薪福社 + 叩丁狼教育 + 悦途出行 + 国信易企签 + 睿颐软件 + 全房通 + 有利网 + 赛维 + 安心保险 + 科达科技 + 会分期 + 会找房 + 会通教育 + 享住智慧 + 兰亮网络 + 桔子数科 + 蓝天教育 + 烟台欣合 + 阿康健康 + 财新传媒 + 新脉远 + 乾动新能源 + 路客精品民宿 + 深圳好尔美 + 浙大睿医 + 深圳市云羿贸易科技有限公司 + 居然之家 + 深圳来电科技有限公司 + 臻善科技 + 中国支付通 + 众网小贷 + 谐云科技 + 浙江甄品 + 深圳海豚网 + 汇通天下 + 九机网 + 有好东西 + 南京智慧盾 + 数跑科技 + 拉粉粉 + 汇通达 + 易宝支付 + 维恩贝特 + 八库 + 大诚若谷 + 杭州华网信息 + 深圳易佰 + 易点生活 + 成都数智索 + 北京超图 + 江西群享科技有限公司 + 宋城独木桥网络有限公司 + 唯小宝(江苏)网络技术有限公司 + 杭州喜团科技 + 海典软件 + 中元健康科技有限公司 + 宿迁民丰农商银行 + 上海海智在线 + 丞家(上海)公寓管理 + 安徽国科新材科 + 商银信支付 + 钛师傅云 + 广州力生信息 + 杭州启舰科技有限公司 + 微链 + 上海美浮特 + 江西群享科技有限公司 + 杭州中威慧云医疗科技有限公司 + 易族智汇(北京) + 佛山宅无限 + F5未来商店 + 重庆雷高科技有限公司 + 甄品信息科技 + 行云全球汇跨境电商(杭州分部) + 世纪加华 + 快陪练 + 西南石油大学 + 厦门服云信息科技有限公司 + 领课网络 + 美通社 + 睿维科技有限公司 + 郑州信源信息技术 + 荣怀集团 + 浙江群集大数据科技有限公司 + 北京易点租有限公司 + 浙江蕙康科技有限公司 + 致远创想 + 深圳智荟物联技术有限公司 + 源讯中国 + 武汉江寓生活服务有限公司 + 大账房 + 上海阳光喔教育科技有限公司 + 北京新学道教育科技有限公司 + 北京悦途出行网络科技公司 + 上海意贝斯特信息技术有限公司 + 御家汇 + 广州社众软件 + 浩鲸科技 + 华宇信息 + 中国云尚科技 + 卫宁健康 + 聚合联动 + 熙菱信息 + 鲸算科技 + 杭州沃朴物联科技有限公司 + 深圳市臻络科技有限公司 + 白云电气 +
+ + + + + + + diff --git a/all/pom.xml b/all/pom.xml new file mode 100644 index 0000000..73074b4 --- /dev/null +++ b/all/pom.xml @@ -0,0 +1,855 @@ + + + + 4.0.0 + + io.seata + seata-all + 1.4.2 + Seata All-in-one ${project.version} + http://seata.io + Seata is an easy-to-use, high-performance, java based, open source distributed transaction solution. + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + Seata + Seata + http://seata.io + + + + + + + + + + + + + + + + + + + + + + 1.8 + 1.8 + UTF-8 + + + + + + io.seata + seata-bom + ${project.version} + pom + import + + + + + + + + io.seata + seata-common + ${project.version} + + + io.seata + seata-config-core + ${project.version} + + + io.seata + seata-config-custom + ${project.version} + + + io.seata + seata-config-apollo + ${project.version} + + + io.seata + seata-config-nacos + ${project.version} + + + io.seata + seata-config-zk + ${project.version} + + + io.seata + seata-config-consul + ${project.version} + + + io.seata + seata-config-etcd3 + ${project.version} + + + io.seata + seata-config-spring-cloud + ${project.version} + + + io.seata + seata-core + ${project.version} + + + io.seata + seata-discovery-core + ${project.version} + + + io.seata + seata-discovery-custom + ${project.version} + + + io.seata + seata-discovery-consul + ${project.version} + + + io.seata + seata-discovery-eureka + ${project.version} + + + io.seata + seata-discovery-nacos + ${project.version} + + + io.seata + seata-discovery-redis + ${project.version} + + + io.seata + seata-discovery-sofa + ${project.version} + + + io.seata + seata-discovery-zk + ${project.version} + + + io.seata + seata-discovery-etcd3 + ${project.version} + + + io.seata + seata-dubbo + ${project.version} + + + io.seata + seata-http + ${project.version} + + + io.seata + seata-dubbo-alibaba + ${project.version} + + + io.seata + seata-sofa-rpc + ${project.version} + + + io.seata + seata-motan + ${project.version} + + + io.seata + seata-rm + ${project.version} + + + io.seata + seata-rm-datasource + ${project.version} + + + io.seata + seata-sqlparser-core + ${project.version} + + + io.seata + seata-sqlparser-antlr + ${project.version} + + + io.seata + seata-sqlparser-druid + ${project.version} + + + io.seata + seata-spring + ${project.version} + + + io.seata + seata-tcc + ${project.version} + + + io.seata + seata-tm + ${project.version} + + + io.seata + seata-serializer-seata + ${project.version} + + + io.seata + seata-serializer-protobuf + ${project.version} + + + io.seata + seata-grpc + ${project.version} + + + io.seata + seata-serializer-kryo + ${project.version} + + + io.seata + seata-serializer-fst + ${project.version} + + + io.seata + seata-serializer-hessian + ${project.version} + + + io.seata + seata-compressor-gzip + ${project.version} + + + io.seata + seata-compressor-7z + ${project.version} + + + io.seata + seata-compressor-bzip2 + ${project.version} + + + io.seata + seata-compressor-zip + ${project.version} + + + io.seata + seata-compressor-lz4 + ${project.version} + + + io.seata + seata-compressor-deflater + ${project.version} + + + + + io.seata + seata-saga-processctrl + ${project.version} + + + io.seata + seata-saga-statelang + ${project.version} + + + io.seata + seata-saga-engine + ${project.version} + + + io.seata + seata-saga-rm + ${project.version} + + + io.seata + seata-saga-tm + ${project.version} + + + io.seata + seata-saga-engine-store + ${project.version} + + + + + org.springframework + spring-context + + + org.springframework + spring-core + + + org.springframework + spring-beans + + + org.springframework + spring-aop + + + org.springframework + spring-webmvc + + + + io.netty + netty-all + + + org.antlr + antlr4 + + + com.alibaba + fastjson + + + com.alibaba + druid + + + com.typesafe + config + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + provided + + + commons-lang + commons-lang + + + org.apache.commons + commons-pool2 + + + commons-pool + commons-pool + + + com.google.protobuf + protobuf-java + + + org.apache.dubbo + dubbo + provided + + + com.alibaba + dubbo + provided + + + cglib + cglib + + + aopalliance + aopalliance + + + com.101tec + zkclient + provided + + + org.apache.zookeeper + zookeeper + provided + + + com.alipay.sofa + registry-client-all + provided + + + com.alibaba.spring + spring-context-support + provided + + + com.alibaba.nacos + nacos-client + provided + + + com.ctrip.framework.apollo + apollo-client + provided + + + redis.clients + jedis + provided + + + com.netflix.eureka + eureka-client + provided + + + com.netflix.archaius + archaius-core + provided + + + com.ecwid.consul + consul-api + provided + + + io.etcd + jetcd-core + provided + + + com.google.guava + guava + + + javax.inject + javax.inject + provided + + + org.apache.httpcomponents + httpclient + provided + + + org.apache.httpcomponents + httpcore + provided + + + com.github.ben-manes.caffeine + caffeine + + + com.alipay.sofa + sofa-rpc-all + provided + + + com.weibo + motan-core + provided + + + com.weibo + motan-transport-netty + provided + + + io.protostuff + protostuff-core + provided + + + io.protostuff + protostuff-runtime + provided + + + + io.grpc + grpc-netty + provided + + + io.grpc + grpc-protobuf + provided + + + io.grpc + grpc-stub + provided + + + mysql + mysql-connector-java + provided + + + org.postgresql + postgresql + provided + + + com.fasterxml.jackson.core + jackson-databind + provided + + + com.esotericsoftware + kryo + provided + + + de.ruedigermoeller + fst + provided + + + com.caucho + hessian + provided + + + de.javakaffee + kryo-serializers + provided + + + org.lz4 + lz4-java + provided + + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + ${user.dir} + + LICENSE + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + true + true + + true + true + + + ${maven.build.timestamp} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.5.1 + + ${maven.compiler.source} + ${maven.compiler.target} + ${project.build.sourceEncoding} + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + ${project.build.sourceEncoding} + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.3 + + + package + + shade + + + true + false + false + true + + + io.seata:seata-common + io.seata:seata-core + io.seata:seata-config-core + io.seata:seata-config-custom + io.seata:seata-config-apollo + io.seata:seata-config-nacos + io.seata:seata-config-zk + io.seata:seata-config-consul + io.seata:seata-config-etcd3 + io.seata:seata-config-spring-cloud + io.seata:seata-discovery-core + io.seata:seata-discovery-custom + io.seata:seata-discovery-consul + io.seata:seata-discovery-eureka + io.seata:seata-discovery-nacos + io.seata:seata-discovery-redis + io.seata:seata-discovery-sofa + io.seata:seata-discovery-zk + io.seata:seata-discovery-etcd3 + io.seata:seata-dubbo + io.seata:seata-dubbo-alibaba + io.seata:seata-motan + io.seata:seata-grpc + io.seata:seata-http + io.seata:seata-rm + io.seata:seata-rm-datasource + io.seata:seata-sqlparser-core + io.seata:seata-sqlparser-antlr + io.seata:seata-sqlparser-druid + io.seata:seata-sofa-rpc + io.seata:seata-spring + io.seata:seata-tcc + io.seata:seata-tm + io.seata:seata-serializer-seata + io.seata:seata-serializer-protobuf + io.seata:seata-serializer-kryo + io.seata:seata-serializer-fst + io.seata:seata-serializer-hessian + + io.seata:seata-saga-processctrl + io.seata:seata-saga-statelang + io.seata:seata-saga-engine + io.seata:seata-saga-rm + io.seata:seata-saga-tm + io.seata:seata-saga-engine-store + + io.seata:seata-compressor-gzip + io.seata:seata-compressor-7z + io.seata:seata-compressor-bzip2 + io.seata:seata-compressor-zip + io.seata:seata-compressor-lz4 + io.seata:seata-compressor-deflater + + + + + + + + META-INF/spring.handlers + + + META-INF/spring.schemas + + + + + *:* + + file.conf + registry.conf + + + + *:* + + META-INF/maven/** + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fantaibao-fantaibao-maven-repository + maven-repository + http://192.168.3.25:18081/nexus/repository/maven-releases/ + + + diff --git a/bom/pom.xml b/bom/pom.xml new file mode 100644 index 0000000..79d0d11 --- /dev/null +++ b/bom/pom.xml @@ -0,0 +1,640 @@ + + + + + io.seata + seata-bom + 1.4.2 + 4.0.0 + pom + + Seata bom ${project.version} + Seata bom + + http://seata.io + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.3.23.RELEASE + 4.1.30.Final + 2.7.0 + 2.6.5 + 5.5.3 + 1.2.73 + 1.5.9 + 1.2.1 + 1.7.22 + 1.2.0 + 2.6 + 2.4.2 + 1.6 + 2.7.0 + 3.4.3 + 3.1 + 1.0 + 0.11 + 3.4.14 + 2.9.1 + 1.0.2 + 0.8.3 + 1.6.0 + 3.2.0 + 0.1.16 + 1.9.5 + 1.4.2 + 1.3.3 + 0.3.0 + 1.11.2 + 27.0.1-jre + 1 + 0.7.6 + 5.2.0 + 4.5.8 + 4.4.11 + 4.8 + 1.1.23 + 2.7.0 + 10.2.0.3.0 + 5.1.35 + 42.1.4 + 1.4.181 + 1.0.0 + 2.9.9 + 1.72 + 1.2 + 1.8 + 1.19 + 1.10.6 + 1.26 + 1.7.1 + + + 1.8 + 1.8 + UTF-8 + 3.7.1 + 1.17.1 + 4.12 + 4.0.2 + 0.42 + 4.0.63 + 2.57 + 2.4.4 + + + + + + + org.springframework + spring-framework-bom + ${spring.version} + pom + import + + + + + io.netty + netty-all + ${netty4.version} + + + com.alibaba + fastjson + ${fastjson.version} + + + org.antlr + antlr4 + ${antlr4.version} + + + com.alibaba + druid + ${druid.version} + + + com.alipay.sofa + sofa-rpc-all + ${sofa.rpc.version} + + + io.protostuff + protostuff-core + ${protostuff.version} + + + io.protostuff + protostuff-runtime + ${protostuff.version} + + + com.typesafe + config + ${config.version} + + + org.yaml + snakeyaml + ${snakeyaml.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + ch.qos.logback + logback-classic + ${logback-classic.version} + + + commons-lang + commons-lang + ${commons-lang.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + commons-pool + commons-pool + ${commons-pool.version} + + + com.google.protobuf + protobuf-java + ${protobuf.version} + + + org.apache.dubbo + dubbo + ${dubbo.version} + + + com.alibaba + dubbo + ${dubbo.alibaba.version} + + + cglib + cglib + ${cglib.version} + + + aopalliance + aopalliance + ${aopalliance.version} + + + com.101tec + zkclient + ${zkclient.version} + + + slf4j-log4j12 + org.slf4j + + + io.netty + netty + + + org.apache.zookeeper + zookeeper + + + + + org.apache.zookeeper + zookeeper + ${apache-zookeeper.version} + + + io.netty + netty + + + + + org.apache.curator + curator-test + ${curator-test.version} + + + com.alipay.sofa + registry-client-all + ${sofa.registry.version} + + + com.alipay.sofa + registry-test + ${sofa.registry.version} + + + log4j-over-slf4j + org.slf4j + + + log4j-jcl + org.apache.logging.log4j + + + log4j-core + org.apache.logging.log4j + + + log4j-api + org.apache.logging.log4j + + + + + com.alibaba.spring + spring-context-support + ${spring-context-support.version} + + + com.alibaba.nacos + nacos-client + ${nacos-client.version} + + + com.ctrip.framework.apollo + apollo-client + ${apollo-client.version} + + + redis.clients + jedis + ${redis-clients.version} + + + com.github.fppt + jedis-mock + ${mock-jedis.version} + test + + + com.netflix.eureka + eureka-client + ${eureka-clients.version} + + + com.netflix.archaius + archaius-core + ${archaius-core.version} + + + com.ecwid.consul + consul-api + ${consul-clients.version} + + + io.etcd + jetcd-core + ${etcd-client-v3.version} + + + io.netty + netty-codec-http + + + io.netty + netty-codec-http2 + + + io.netty + netty-handler-proxy + + + io.netty + netty-handler + + + + + com.google.guava + guava + ${guava.version} + + + io.etcd + jetcd-launcher + ${etcd-client-v3.version} + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + javax.inject + javax.inject + ${javax-inject.version} + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + org.apache.httpcomponents + httpcore + ${httpcore.version} + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + mysql + mysql-connector-java + ${mysql.client.version} + + + org.postgresql + postgresql + ${postgres.client.version} + + + com.weibo + motan-core + ${motan.version} + + + slf4j-log4j12 + org.slf4j + + + + + com.weibo + motan-transport-netty + ${motan.version} + + + slf4j-log4j12 + org.slf4j + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.beust + jcommander + ${jcommander.version} + + + + io.grpc + grpc-testing + test + ${grpc.version} + + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + com.esotericsoftware + kryo + ${kryo.version} + + + de.javakaffee + kryo-serializers + ${kryo-serializers.version} + + + com.caucho + hessian + ${hessian.version} + + + de.ruedigermoeller + fst + ${fst.version} + + + org.apache.commons + commons-dbcp2 + ${commons-dbcp2.version} + + + com.zaxxer + HikariCP + ${hikari.version} + + + com.h2database + h2 + ${h2.version} + + + javax.annotation + javax.annotation-api + ${annotation.api.version} + provided + + + org.tukaani + xz + ${xz.version} + + + org.apache.commons + commons-compress + ${commons-compress.version} + + + org.apache.ant + ant + ${ant.version} + + + org.lz4 + lz4-java + ${lz4.version} + + + org.codehaus.groovy + groovy-all + ${groovy.version} + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.platform + junit-platform-launcher + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fantaibao-fantaibao-maven-repository + maven-repository + http://192.168.3.25:18081/nexus/repository/maven-releases/ + + + diff --git a/changeVersion.sh b/changeVersion.sh new file mode 100644 index 0000000..6a2347f --- /dev/null +++ b/changeVersion.sh @@ -0,0 +1,5 @@ +echo "./changeVersion.sh oldVersion newVersion" +echo $1 +echo $2 +find ./ -name pom.xml | grep -v target | xargs perl -pi -e "s|$1|$2|g" +#find ./ -name Version.java | grep -v target | xargs perl -pi -e "s|$1|$2|g" \ No newline at end of file diff --git a/changes/1.4.2.md b/changes/1.4.2.md new file mode 100644 index 0000000..204f9ae --- /dev/null +++ b/changes/1.4.2.md @@ -0,0 +1,176 @@ +### 1.4.2 + + [source](https://github.com/seata/seata/archive/v1.4.2.zip) | + [binary](https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip) + +
+ Release notes + + + ### Seata 1.4.2 + +Seata 1.4.2 发布。 + +Seata 是一款开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。 + +此版本更新如下: + + ### feature: + + - [[#3172](https://github.com/seata/seata/pull/3172)] 支持 AT 模式 undo_log 压缩模式 + - [[#3372](https://github.com/seata/seata/pull/3372)] 支持saga模式下用户自定义是否更新最后一次重试日志 + - [[#3411](https://github.com/seata/seata/pull/3411)] 支持seata-server 线程池参数可配置 + - [[#3348](https://github.com/seata/seata/pull/3348)] 支持 TC 存储模式使用 redis-sentinel + - [[#2667](https://github.com/seata/seata/pull/2667)] 支持使用db和redis存储模式时密码的加解密 + - [[#3427](https://github.com/seata/seata/pull/3427)] 支持分布式锁接口 + - [[#3443](https://github.com/seata/seata/pull/3443)] 支持将seata-server的日志发送到logstash或kafka中 + - [[#3486](https://github.com/seata/seata/pull/3486)] 支持Metrics增加事务分组属性 + - [[#3317](https://github.com/seata/seata/pull/3317)] 支持当zookeeper作为配置中心时从单node获取全部配置 + - [[#2933](https://github.com/seata/seata/pull/2933)] 支持mysql antlr sqlparser + - [[#3228](https://github.com/seata/seata/pull/3228)] 支持自定义序列化插件 + - [[#3516](https://github.com/seata/seata/pull/3516)] 支持 consul 作为注册中心和配置中心时的 acl-token + - [[#3116](https://github.com/seata/seata/pull/3116)] 支持配置 apollo 配置中心配置 configService 和 cluster + - [[#3468](https://github.com/seata/seata/pull/3468)] 支持saga模式下任务循环执行 + - [[#3447](https://github.com/seata/seata/pull/3447)] 支持日志框架中事务上下文的打印 + + + ### bugfix: + + - [[#3258](https://github.com/seata/seata/pull/3258)] 修复AsyncWorker潜在的OOM问题 + - [[#3293](https://github.com/seata/seata/pull/3293)] 修复配置缓存获取值类型不匹配的问题 + - [[#3241](https://github.com/seata/seata/pull/3241)] 禁止在多SQL的情况下使用 limit 和 order by 语法 + - [[#3406](https://github.com/seata/seata/pull/3406)] 修复当config.txt中包含特殊字符时无法推送至 nacos 的问题 + - [[#3367](https://github.com/seata/seata/pull/3367)] 修复最后一个XA分支二阶段时偶发无法回滚的异常 + - [[#3418](https://github.com/seata/seata/pull/3418)] 修复 getGeneratedKeys 可能会取到历史的主键的问题 + - [[#3448](https://github.com/seata/seata/pull/3448)] 修复多个锁竞争失败时,仅删除单个锁,并优化锁竞争逻辑提升处理性能 + - [[#3408](https://github.com/seata/seata/pull/3408)] 修复jar运行模式第三方依赖分离打包时的NPE问题 + - [[#3431](https://github.com/seata/seata/pull/3431)] 修复在读取配置时Property Bean可能未初始化的问题 + - [[#3413](https://github.com/seata/seata/pull/3413)] 修复回滚到savepoint以及releaseSavepoint的逻辑 + - [[#3451](https://github.com/seata/seata/pull/3451)] 修复autoCommit=true,全局锁竞争失败时的脏写问题 + - [[#3481](https://github.com/seata/seata/pull/3481)] 修复当 consul client 抛出异常时导致刷新任务中断的问题 + - [[#3491](https://github.com/seata/seata/pull/3491)] 修复README.md文件中的拼写错误 + - [[#3531](https://github.com/seata/seata/pull/3531)] 修复RedisTransactionStoreManager 获取 brachTransaction 可能的 NPE 问题 + - [[#3500](https://github.com/seata/seata/pull/3500)] 修复 oracle 和 postgreSql 无法获取 column info 的问题 + - [[#3560](https://github.com/seata/seata/pull/3560)] 修复 Committing 状态的事务异步任务没有时间阈值和无法进行事务恢复的问题 + - [[#3555](https://github.com/seata/seata/pull/3555)] 通过setBytes代替setBlob,避免高版本jdbc驱动工作异常 + - [[#3540](https://github.com/seata/seata/pull/3540)] 修复server发布打包时缺失文件的问题 + - [[#3597](https://github.com/seata/seata/pull/3597)] 修复可能的 NPE问题 + - [[#3568](https://github.com/seata/seata/pull/3568)] 修复自动数据源代理因 ConcurrentHashMap.computeIfAbsent 导致的死锁问题 + - [[#3402](https://github.com/seata/seata/pull/3402)] 修复更新SQL中字段名含有库名无法解析更新列的问题 + - [[#3464](https://github.com/seata/seata/pull/3464)] 修复测试用例空指针异常和StackTraceLogger中错误的日志格式. + - [[#3522](https://github.com/seata/seata/pull/3522)] 修复当 DML 影响行数为0时注册分支和插入undo_log的问题 + - [[#3635](https://github.com/seata/seata/pull/3635)] 修复zookeeper 配置变更无法推送通知的问题 + - [[#3133](https://github.com/seata/seata/pull/3133)] 修复某些场景下无法重试全局锁的问题 + - [[#3156](https://github.com/seata/seata/pull/3156)] 修复嵌套代理类无法 获取target的问题 + + + ### optimize: + + - [[#3341](https://github.com/seata/seata/pull/3341)] 优化获取指定配置文件的路径格式问题 + - [[#3385](https://github.com/seata/seata/pull/3385)] 优化 GitHub Actions 配置,修复单测失败问题 + - [[#3175](https://github.com/seata/seata/pull/3175)] 支持雪花算法时钟回拨 + - [[#3291](https://github.com/seata/seata/pull/3291)] 优化mysql连接参数 + - [[#3336](https://github.com/seata/seata/pull/3336)] 支持使用System.getProperty获取Netty配置参数 + - [[#3369](https://github.com/seata/seata/pull/3369)] 添加github action的dockerHub秘钥 + - [[#3343](https://github.com/seata/seata/pull/3343)] 将CI程序从Travis CI迁移到Github Actions + - [[#3397](https://github.com/seata/seata/pull/3397)] 增加代码变更记录 + - [[#3303](https://github.com/seata/seata/pull/3303)] 支持从nacos单一dataId中读取所有配置 + - [[#3380](https://github.com/seata/seata/pull/3380)] 优化 globalTransactionScanner 中的 DISABLE_GLOBAL_TRANSACTION listener + - [[#3123](https://github.com/seata/seata/pull/3123)] 优化 seata-server 打包策略 + - [[#3415](https://github.com/seata/seata/pull/3415)] 优化 maven 打包时清除 distribution 目录 + - [[#3316](https://github.com/seata/seata/pull/3316)] 优化读取配置值时属性bean未初始化的问题 + - [[#3420](https://github.com/seata/seata/pull/3420)] 优化枚举类的使用并添加单元测试 + - [[#3533](https://github.com/seata/seata/pull/3533)] 支持获取当前事务角色 + - [[#3436](https://github.com/seata/seata/pull/3436)] 优化SQLType类中的错别字 + - [[#3439](https://github.com/seata/seata/pull/3439)] 调整springApplicationContextProvider order以使其可以在xml bean之前被调用 + - [[#3248](https://github.com/seata/seata/pull/3248)] 优化负载均衡配置迁移到client节点下 + - [[#3441](https://github.com/seata/seata/pull/3441)] 优化starter的自动配置处理 + - [[#3466](https://github.com/seata/seata/pull/3466)] 优化使用equalsIgnoreCase() 进行字符串比较 + - [[#3476](https://github.com/seata/seata/pull/3476)] 支持 server 参数传入hostname时自动将其转换为 ip + - [[#3236](https://github.com/seata/seata/pull/3236)] 优化执行解锁操作的条件,减少不必要的 unlock 操作 + - [[#3485](https://github.com/seata/seata/pull/3485)] 删除 ConfigurationFactory 中无用的代码 + - [[#3505](https://github.com/seata/seata/pull/3505)] 删除 GlobalTransactionScanner 中无用的 if 判断 + - [[#3544](https://github.com/seata/seata/pull/3544)] 优化无法通过Statement#getGeneratedKeys时,只能获取到批量插入的第一个主键的问题 + - [[#3549](https://github.com/seata/seata/pull/3549)] 统一DB存储模式下不同表中的xid字段的长度 + - [[#3551](https://github.com/seata/seata/pull/3551)] 调大RETRY_DEAD_THRESHOLD的值以及设置成可配置 + - [[#3589](https://github.com/seata/seata/pull/3589)] 使用JUnit API做异常检查 + - [[#3601](https://github.com/seata/seata/pull/3601)] 使`LoadBalanceProperties`与`spring-boot:2.x`及以上版本兼容 + - [[#3513](https://github.com/seata/seata/pull/3513)] Saga SpringBeanService调用器支持切换 json 解析器 + - [[#3318](https://github.com/seata/seata/pull/3318)] 支持 CLIENT_TABLE_META_CHECKER_INTERVAL 可配置化 + - [[#3371](https://github.com/seata/seata/pull/3371)] 支持 metric 按 applicationId 分组 + - [[#3459](https://github.com/seata/seata/pull/3459)] 删除重复的ValidadAddress代码 + - [[#3215](https://github.com/seata/seata/pull/3215)] 优化seata-server 在file模式下启动时的reload逻辑 + - [[#3631](https://github.com/seata/seata/pull/3631)] 优化 nacos-config.py 脚本的入参问题 + - [[#3638](https://github.com/seata/seata/pull/3638)] 优化 update 和 delete 的 SQL 不支持 join 的错误提示 + - [[#3523](https://github.com/seata/seata/pull/3523)] 优化当使用oracle时调用releaseSavepoint()方法报异常的问题 + - [[#3458](https://github.com/seata/seata/pull/3458)] 还原已删除的md + - [[#3574](https://github.com/seata/seata/pull/3574)] 修复EventBus.java文件中注释拼写错误 + - [[#3573](https://github.com/seata/seata/pull/3573)] 修复 README.md 文件中设计器路径错误 + - [[#3662](https://github.com/seata/seata/pull/3662)] 更新gpg密钥对 + - [[#3664](https://github.com/seata/seata/pull/3664)] 优化 javadoc + - [[#3637](https://github.com/seata/seata/pull/3637)] 登记使用seata的公司和1.4.2版本包含的新增pr信息 + + ### test + + - [[#3381](https://github.com/seata/seata/pull/3381)] 添加 TmClient 的测试用例 + - [[#3607](https://github.com/seata/seata/pull/3607)] 修复 EventBus 的单元测试问题 + - [[#3579](https://github.com/seata/seata/pull/3579)] 添加 StringFormatUtils 测试用例 + - [[#3365](https://github.com/seata/seata/pull/3365)] 修复ParameterParserTest测试用例 + - [[#3359](https://github.com/seata/seata/pull/3359)] 删除未使用的测试用例 + - [[#3383](https://github.com/seata/seata/pull/3383)] 优化StatementProxyTest单元测试 + - [[#3578](https://github.com/seata/seata/pull/3578)] 修复单元测试case里的UnfinishedStubbing异常 + + + 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 + + - [slievrly](https://github.com/slievrly) + - [caohdgege](https://github.com/caohdgege) + - [a364176773](https://github.com/a364176773) + - [wangliang181230](https://github.com/wangliang181230) + - [xingfudeshi](https://github.com/xingfudeshi) + - [jsbxyyx](https://github.com/jsbxyyx) + - [selfishlover](https://github.com/selfishlover) + - [l8189352](https://github.com/l81893521) + - [Rubbernecker](https://github.com/Rubbernecker) + - [lj2018110133](https://github.com/lj2018110133) + - [github-ganyu](https://github.com/github-ganyu) + - [dmego](https://github.com/dmego) + - [spilledyear](https://github.com/spilledyear) + - [hoverruan](https://github.com/hoverruan ) + - [anselleeyy](https://github.com/anselleeyy) + - [Ifdevil](https://github.com/Ifdevil) + - [lvxianzheng](https://github.com/lvxianzheng) + - [MentosL](https://github.com/MentosL) + - [lian88jian](https://github.com/lian88jian) + - [litianyu1992](https://github.com/litianyu1992) + - [xyz327](https://github.com/xyz327) + - [13414850431](https://github.com/13414850431) + - [xuande](https://github.com/xuande) + - [tanggen](https://github.com/tanggen) + - [eas5](https://github.com/eas5) + - [nature80](https://github.com/nature80) + - [ls9527](https://github.com/ls9527) + - [drgnchan](https://github.com/drgnchan) + - [imyangyong](https://github.com/imyangyong) + - [sunlggggg](https://github.com/sunlggggg) + - [long187](https://github.com/long187) + - [h-zhi](https://github.com/h-zhi) + - [StellaiYang](https://github.com/StellaiYang) + - [slinpq](https://github.com/slinpq) + - [sustly](https://github.com/sustly) + - [cznc](https://github.com/cznc) + - [squallliu](https://github.com/squallliu) + - [81519434](https://github.com/81519434) + - [luoxn28](https://github.com/luoxn28) + + +同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 + + #### Link + + - **Seata:** https://github.com/seata/seata + - **Seata-Samples:** https://github.com/seata/seata-samples + - **Release:** https://github.com/seata/seata/releases + - **WebSite:** https://seata.io + +
diff --git a/changes/en-us/1.4.2.md b/changes/en-us/1.4.2.md new file mode 100644 index 0000000..e03827e --- /dev/null +++ b/changes/en-us/1.4.2.md @@ -0,0 +1,177 @@ +### 1.4.2 + + [source](https://github.com/seata/seata/archive/v1.4.2.zip) | + [binary](https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip) + +
+ Release notes + + + ### Seata 1.4.2 + + Seata 1.4.2 Released. + + Seata is an easy-to-use, high-performance, open source distributed transaction solution. + + The version is updated as follows: + + ### feature: + + - [[#3172](https://github.com/seata/seata/pull/3172)] support undo_loge compression mode in AT + - [[#3372](https://github.com/seata/seata/pull/3372)] Saga support customize whether update last retry log + - [[#3411](https://github.com/seata/seata/pull/3411)] support seata server thread pool parameters configuration + - [[#3348](https://github.com/seata/seata/pull/3348)] support redis sentinel storage mode in TC + - [[#2667](https://github.com/seata/seata/pull/2667)] support password decryption when using db and redis storage mode + - [[#3427](https://github.com/seata/seata/pull/3427)] add distributed lock interface + - [[#3443](https://github.com/seata/seata/pull/3443)] send the `seata-server` log to `logstash` or `kafka` + - [[#3486](https://github.com/seata/seata/pull/3486)] add transaction service group for metric + - [[#3317](https://github.com/seata/seata/pull/3317)] support to obtain multiple configurations through a single node when using zookeeper as configuration center + - [[#2933](https://github.com/seata/seata/pull/2933)] add antlr for mysql sqlparser + - [[#3228](https://github.com/seata/seata/pull/3228)] support custom serialization plugin + - [[#3516](https://github.com/seata/seata/pull/3516)] support acl-token when consul is used registry and configuration center + - [[#3116](https://github.com/seata/seata/pull/3116)] support configuring apolloService and apolloCluster + - [[#3468](https://github.com/seata/seata/pull/3468)] saga support loop execution on state + - [[#3447](https://github.com/seata/seata/pull/3447)] support Transaction context printing in logging framework + + + ### bugfix: + + - [[#3258](https://github.com/seata/seata/pull/3258)] fix AsyncWorker potential OOM problem + - [[#3293](https://github.com/seata/seata/pull/3293)] fix configuration cache get value type mismatch exception + - [[#3241](https://github.com/seata/seata/pull/3241)] forbidden use order by or limit in multi sql + - [[#3406](https://github.com/seata/seata/pull/3406)] fix the value can not be push to nacos when special charset in config.txt + - [[#3418](https://github.com/seata/seata/pull/3418)] fix getGeneratedKeys may get history pk + - [[#3408](https://github.com/seata/seata/pull/3408)] fix the NPE problem of jar running mode when the third-dependency on separate packaging + - [[#3431](https://github.com/seata/seata/pull/3431)] fix property bean may not be initialized when reading configuration + - [[#3413](https://github.com/seata/seata/pull/3413)] fix the logic of rollback to savepoint and release to savepoint + - [[#3367](https://github.com/seata/seata/pull/3367)] when the xa branch is rollback, it cannot be executed due to idle state + - [[#3448](https://github.com/seata/seata/pull/3448)] reduce unnecessary competition and remove missing locks + - [[#3451](https://github.com/seata/seata/pull/3451)] fix set auto-commit to true when local transactions are not being used. Failure to compete for a lock causes the global transaction to exit, invaliding the global row lock and dirty writing of the data. + - [[#3481](https://github.com/seata/seata/pull/3481)] fix seata node refresh failure because of consul client throws exceptions + - [[#3491](https://github.com/seata/seata/pull/3491)] fix typo in README.md + - [[#3531](https://github.com/seata/seata/pull/3531)] fix the NPE of RedisTransactionStoreManager when get branch transactions + - [[#3500](https://github.com/seata/seata/pull/3500)] fix oracle and postgreSQL can't query column info + - [[#3560](https://github.com/seata/seata/pull/3560)] fix the problem that the asynchronous task of the transactions in the committing state has no time threshold and cannot recover the transaction + - [[#3555](https://github.com/seata/seata/pull/3555)] do not call setBlob to invalid the jdbc exception + - [[#3540](https://github.com/seata/seata/pull/3540)] fix server distribution missing files + - [[#3597](https://github.com/seata/seata/pull/3597)] fix the possible NPE + - [[#3568](https://github.com/seata/seata/pull/3568)] fix automatic datasource agent caused by ConcurrentHashMap.computeIfAbsent Deadlock problem + - [[#3402](https://github.com/seata/seata/pull/3402)] fix the problem that the updated column cannot be resolved because the field name in the updated SQL contains the database name + - [[#3464](https://github.com/seata/seata/pull/3464)] fix test case NPE and StackTraceLogger's log. + - [[#3522](https://github.com/seata/seata/pull/3522)] fix register branch and store undolog when AT branch does not need compete lock + - [[#3635](https://github.com/seata/seata/pull/3635)] fix pushing notification failed when the configuration changed in zookeeper + - [[#3133](https://github.com/seata/seata/pull/3133)] fix the case that could not retry acquire global lock + - [[#3156](https://github.com/seata/seata/pull/3156)] optimize the logic of SpringProxyUtils.findTargetClass + + + ### optimize: + + - [[#3341](https://github.com/seata/seata/pull/3341)] optimize the format of the path to the specified configuration file + - [[#3385](https://github.com/seata/seata/pull/3385)] optimize github action and fix unit test failure + - [[#3175](https://github.com/seata/seata/pull/3175)] improve UUIDGenerator using "history time" version of snowflake algorithm + - [[#3291](https://github.com/seata/seata/pull/3291)] mysql jdbc connect param + - [[#3336](https://github.com/seata/seata/pull/3336)] support using System.getProperty to get netty config property + - [[#3369](https://github.com/seata/seata/pull/3369)] add github action secrets env for dockerHub + - [[#3343](https://github.com/seata/seata/pull/3343)] Migrate CI provider from Travis CI to Github Actions + - [[#3397](https://github.com/seata/seata/pull/3397)] add the change records folder + - [[#3303](https://github.com/seata/seata/pull/3303)] supports reading all configurations from a single Nacos dataId + - [[#3380](https://github.com/seata/seata/pull/3380)] globalTransactionScanner listener optimize + - [[#3123](https://github.com/seata/seata/pull/3123)] optimize the packing strategy of seata-server + - [[#3415](https://github.com/seata/seata/pull/3415)] optimize maven clean plugin to clear the distribution directory + - [[#3316](https://github.com/seata/seata/pull/3316)] optimize the property bean may not be initialized while reading config value + - [[#3420](https://github.com/seata/seata/pull/3420)] optimize enumerated classes and add unit tests + - [[#3533](https://github.com/seata/seata/pull/3533)] added interface to get current transaction role + - [[#3436](https://github.com/seata/seata/pull/3436)] optimize typo in SQLType class + - [[#3439](https://github.com/seata/seata/pull/3439)] adjust the order of springApplicationContextProvider so that it can be called before the XML bean + - [[#3248](https://github.com/seata/seata/pull/3248)] optimize the config of load-balance migration to belong the client node + - [[#3441](https://github.com/seata/seata/pull/3441)] optimize the auto-configuration processing of starter + - [[#3466](https://github.com/seata/seata/pull/3466)] String comparison uses equalsIgnoreCase() + - [[#3476](https://github.com/seata/seata/pull/3476)] support when the server parameter passed is hostname, it will be automatically converted to IP + - [[#3236](https://github.com/seata/seata/pull/3236)] optimize the conditions for executing unlocking + - [[#3485](https://github.com/seata/seata/pull/3485)] optimize useless codes in ConfigurationFactory + - [[#3505](https://github.com/seata/seata/pull/3505)] optimize useless if judgments in the GlobalTransactionScanner class + - [[#3544](https://github.com/seata/seata/pull/3544)] optimize the get pks by auto when auto generated keys is false + - [[#3549](https://github.com/seata/seata/pull/3549)] unified the length of xid in different tables when using DB storage mode + - [[#3551](https://github.com/seata/seata/pull/3551)] make RETRY_DEAD_THRESHOLD bigger and configurable + - [[#3589](https://github.com/seata/seata/pull/3589)] Changed exception check by JUnit API usage + - [[#3601](https://github.com/seata/seata/pull/3601)] make `LoadBalanceProperties` compatible with `spring-boot:2.x` and above + - [[#3513](https://github.com/seata/seata/pull/3513)] Saga SpringBeanService invoker support switch json parser + - [[#3318](https://github.com/seata/seata/pull/3318)] make CLIENT_TABLE_META_CHECKER_INTERVAL configurable + - [[#3371](https://github.com/seata/seata/pull/3371)] add applicationId for metric + - [[#3459](https://github.com/seata/seata/pull/3459)] remove duplicate validAddress code + - [[#3215](https://github.com/seata/seata/pull/3215)] opt the reload during startup in file mode + - [[#3631](https://github.com/seata/seata/pull/3631)] optimize nacos-config.py parameter + - [[#3638](https://github.com/seata/seata/pull/3638)] optimize the error when use update or delete with join in sql + - [[#3523](https://github.com/seata/seata/pull/3523)] optimize release savepoint when use oracle + - [[#3458](https://github.com/seata/seata/pull/3458)] reversion the deleted md + - [[#3574](https://github.com/seata/seata/pull/3574)] repair Spelling errors in comments in EventBus.java files + - [[#3573](https://github.com/seata/seata/pull/3573)] fix designer directory path in README.md + - [[#3662](https://github.com/seata/seata/pull/3662)] update gpg key + - [[#3664](https://github.com/seata/seata/pull/3664)] optimize some javadocs + - [[#3637](https://github.com/seata/seata/pull/3637)] register the participating companies and pull request information + + ### test + + - [[#3381](https://github.com/seata/seata/pull/3381)] test case for tmClient + - [[#3607](https://github.com/seata/seata/pull/3607)] fixed bugs in EventBus unit tests + - [[#3579](https://github.com/seata/seata/pull/3579)] add test case for StringFormatUtils + - [[#3365](https://github.com/seata/seata/pull/3365)] optimize ParameterParserTest test case failed + - [[#3359](https://github.com/seata/seata/pull/3359)] remove unused test case + - [[#3578](https://github.com/seata/seata/pull/3578)] fix UnfinishedStubbing Exception in unit test case + - [[#3383](https://github.com/seata/seata/pull/3383)] optimize StatementProxyTest unit test + + + + Thanks to these contributors for their code commits. Please report an unintended omission. + + - [slievrly](https://github.com/slievrly) + - [caohdgege](https://github.com/caohdgege) + - [a364176773](https://github.com/a364176773) + - [wangliang181230](https://github.com/wangliang181230) + - [xingfudeshi](https://github.com/xingfudeshi) + - [jsbxyyx](https://github.com/jsbxyyx) + - [selfishlover](https://github.com/selfishlover) + - [l8189352](https://github.com/l81893521) + - [Rubbernecker](https://github.com/Rubbernecker) + - [lj2018110133](https://github.com/lj2018110133) + - [github-ganyu](https://github.com/github-ganyu) + - [dmego](https://github.com/dmego) + - [spilledyear](https://github.com/spilledyear) + - [hoverruan](https://github.com/hoverruan ) + - [anselleeyy](https://github.com/anselleeyy) + - [Ifdevil](https://github.com/Ifdevil) + - [lvxianzheng](https://github.com/lvxianzheng) + - [MentosL](https://github.com/MentosL) + - [lian88jian](https://github.com/lian88jian) + - [litianyu1992](https://github.com/litianyu1992) + - [xyz327](https://github.com/xyz327) + - [13414850431](https://github.com/13414850431) + - [xuande](https://github.com/xuande) + - [tanggen](https://github.com/tanggen) + - [eas5](https://github.com/eas5) + - [nature80](https://github.com/nature80) + - [ls9527](https://github.com/ls9527) + - [drgnchan](https://github.com/drgnchan) + - [imyangyong](https://github.com/imyangyong) + - [sunlggggg](https://github.com/sunlggggg) + - [long187](https://github.com/long187) + - [h-zhi](https://github.com/h-zhi) + - [StellaiYang](https://github.com/StellaiYang) + - [slinpq](https://github.com/slinpq) + - [sustly](https://github.com/sustly) + - [cznc](https://github.com/cznc) + - [squallliu](https://github.com/squallliu) + - [81519434](https://github.com/81519434) + - [luoxn28](https://github.com/luoxn28) + + Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. + + #### Link + + - **Seata:** https://github.com/seata/seata + - **Seata-Samples:** https://github.com/seata/seata-samples + - **Release:** https://github.com/seata/seata/releases + - **WebSite:** https://seata.io + + +
diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..e60b1be --- /dev/null +++ b/codecov.yml @@ -0,0 +1,25 @@ +codecov: + require_ci_to_pass: yes +coverage: + status: + patch: no + project: + default: + threshold: 1% + if_not_found: success + changes: no + precision: 2 + range: "50...100" +ignore: + - "test/.*" + - ".github/.*" + - ".mvn/.*" + - ".style/.*" + - "*.md" + - "rm-datasource/src/test/java/io/seata/rm/datasource/mock" + - "sqlparser/seata-sqlparser-antlr/src/main/java/io/seata/sqlparser/antlr/mysql/antlr/.*" + - "sqlparser/seata-sqlparser-antlr/src/main/java/io/seata/sqlparser/antlr/mysql/parser/.*" +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: no diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..90171ed --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,43 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-common + jar + seata-common ${project.version} + + + io.netty + netty-all + + + org.slf4j + slf4j-api + + + commons-lang + commons-lang + + + diff --git a/common/src/main/java/io/seata/common/Constants.java b/common/src/main/java/io/seata/common/Constants.java new file mode 100644 index 0000000..bdec1e6 --- /dev/null +++ b/common/src/main/java/io/seata/common/Constants.java @@ -0,0 +1,160 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common; + +import java.nio.charset.Charset; + +/** + * The type Constants. + * + * @author slievrly + */ +public interface Constants { + + /** + * The constant IP_PORT_SPLIT_CHAR. + */ + String IP_PORT_SPLIT_CHAR = ":"; + /** + * The constant CLIENT_ID_SPLIT_CHAR. + */ + String CLIENT_ID_SPLIT_CHAR = ":"; + /** + * The constant ENDPOINT_BEGIN_CHAR. + */ + String ENDPOINT_BEGIN_CHAR = "/"; + /** + * The constant DBKEYS_SPLIT_CHAR. + */ + String DBKEYS_SPLIT_CHAR = ","; + + /** + * The constant ROW_LOCK_KEY_SPLIT_CHAR. + */ + String ROW_LOCK_KEY_SPLIT_CHAR = ";"; + + /** + * the start time of transaction + */ + String START_TIME = "start-time"; + + /** + * app name + */ + String APP_NAME = "appName"; + + /** + * TCC start time + */ + String ACTION_START_TIME = "action-start-time"; + + /** + * TCC name + */ + String ACTION_NAME = "actionName"; + + /** + * phase one method name + */ + String PREPARE_METHOD = "sys::prepare"; + + /** + * phase two commit method name + */ + String COMMIT_METHOD = "sys::commit"; + + /** + * phase two rollback method name + */ + String ROLLBACK_METHOD = "sys::rollback"; + + /** + * host ip + */ + String HOST_NAME = "host-name"; + + /** + * The constant TCC_METHOD_RESULT. + */ + String TCC_METHOD_RESULT = "result"; + + /** + * The constant TCC_METHOD_ARGUMENTS. + */ + String TCC_METHOD_ARGUMENTS = "arguments"; + + /** + * transaction context + */ + String TCC_ACTIVITY_CONTEXT = "activityContext"; + + /** + * branch context + */ + String TCC_ACTION_CONTEXT = "actionContext"; + + /** + * default charset name + */ + String DEFAULT_CHARSET_NAME = "UTF-8"; + + /** + * default charset is utf-8 + */ + Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_CHARSET_NAME); + /** + * The constant OBJECT_KEY_SPRING_APPLICATION_CONTEXT + */ + String OBJECT_KEY_SPRING_APPLICATION_CONTEXT = "springApplicationContext"; + /** + * The constant BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER + */ + String BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER = "springApplicationContextProvider"; + /** + * The constant BEAN_NAME_FAILURE_HANDLER + */ + String BEAN_NAME_FAILURE_HANDLER = "failureHandler"; + /** + * The constant SAGA_TRANS_NAME_PREFIX + */ + String SAGA_TRANS_NAME_PREFIX = "$Saga_"; + + /** + * The constant RETRY_ROLLBACKING + */ + String RETRY_ROLLBACKING = "RetryRollbacking"; + + /** + * The constant RETRY_COMMITTING + */ + String RETRY_COMMITTING = "RetryCommitting"; + + /** + * The constant ASYNC_COMMITTING + */ + String ASYNC_COMMITTING = "AsyncCommitting"; + + /** + * The constant TX_TIMEOUT_CHECK + */ + String TX_TIMEOUT_CHECK = "TxTimeoutCheck"; + + /** + * The constant UNDOLOG_DELETE + */ + String UNDOLOG_DELETE = "UndologDelete"; + +} diff --git a/common/src/main/java/io/seata/common/DefaultValues.java b/common/src/main/java/io/seata/common/DefaultValues.java new file mode 100644 index 0000000..b960a88 --- /dev/null +++ b/common/src/main/java/io/seata/common/DefaultValues.java @@ -0,0 +1,122 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author xingfudeshi@gmail.com + */ +public interface DefaultValues { + int DEFAULT_CLIENT_LOCK_RETRY_INTERVAL = 10; + int DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES = 10; + int DEFAULT_CLIENT_LOCK_RETRY_TIMES = 30; + boolean DEFAULT_CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT = true; + int DEFAULT_LOG_EXCEPTION_RATE = 100; + int DEFAULT_CLIENT_ASYNC_COMMIT_BUFFER_LIMIT = 10000; + int DEFAULT_TM_DEGRADE_CHECK_PERIOD = 2000; + int DEFAULT_CLIENT_REPORT_RETRY_COUNT = 5; + boolean DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE = false; + boolean DEFAULT_CLIENT_TABLE_META_CHECK_ENABLE = false; + long DEFAULT_TABLE_META_CHECKER_INTERVAL = 60000L; + boolean DEFAULT_TM_DEGRADE_CHECK = false; + boolean DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE = false; + boolean DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE = false; + boolean DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE = false; + + /** + * Shutdown timeout default 3s + */ + int DEFAULT_SHUTDOWN_TIMEOUT_SEC = 3; + int DEFAULT_SELECTOR_THREAD_SIZE = 1; + int DEFAULT_BOSS_THREAD_SIZE = 1; + + + String DEFAULT_SELECTOR_THREAD_PREFIX = "NettyClientSelector"; + String DEFAULT_WORKER_THREAD_PREFIX = "NettyClientWorkerThread"; + boolean DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST = true; + + + String DEFAULT_BOSS_THREAD_PREFIX = "NettyBoss"; + String DEFAULT_NIO_WORKER_THREAD_PREFIX = "NettyServerNIOWorker"; + String DEFAULT_EXECUTOR_THREAD_PREFIX = "NettyServerBizHandler"; + + boolean DEFAULT_TRANSPORT_HEARTBEAT = true; + boolean DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION = true; + String DEFAULT_TRANSACTION_UNDO_LOG_SERIALIZATION = "jackson"; + boolean DEFAULT_ONLY_CARE_UPDATE_COLUMNS = true; + /** + * The constant DEFAULT_TRANSACTION_UNDO_LOG_TABLE. + */ + String DEFAULT_TRANSACTION_UNDO_LOG_TABLE = "undo_log"; + /** + * The constant DEFAULT_STORE_DB_GLOBAL_TABLE. + */ + String DEFAULT_STORE_DB_GLOBAL_TABLE = "global_table"; + + /** + * The constant DEFAULT_STORE_DB_BRANCH_TABLE. + */ + String DEFAULT_STORE_DB_BRANCH_TABLE = "branch_table"; + + /** + * The constant DEFAULT_LOCK_DB_TABLE. + */ + String DEFAULT_LOCK_DB_TABLE = "lock_table"; + + int DEFAULT_TM_COMMIT_RETRY_COUNT = 5; + int DEFAULT_TM_ROLLBACK_RETRY_COUNT = 5; + int DEFAULT_GLOBAL_TRANSACTION_TIMEOUT = 60000; + + String DEFAULT_TX_GROUP = "my_test_tx_group"; + String DEFAULT_TC_CLUSTER = "default"; + String DEFAULT_GROUPLIST = "127.0.0.1:8091"; + + String DEFAULT_DATA_SOURCE_PROXY_MODE = "AT"; + + boolean DEFAULT_DISABLE_GLOBAL_TRANSACTION = false; + + int SERVER_DEFAULT_PORT = 8091; + String SERVER_DEFAULT_STORE_MODE = "file"; + long SERVER_DEFAULT_NODE = ThreadLocalRandom.current().nextLong(1024); + + String DEFAULT_SAGA_JSON_PARSER = "fastjson"; + + boolean DEFAULT_SERVER_ENABLE_CHECK_AUTH = true; + + String DEFAULT_LOAD_BALANCE = "RandomLoadBalance"; + int VIRTUAL_NODES_DEFAULT = 10; + + /** + * the constant DEFAULT_CLIENT_UNDO_COMPRESS_ENABLE + */ + boolean DEFAULT_CLIENT_UNDO_COMPRESS_ENABLE = true; + + /** + * the constant DEFAULT_CLIENT_UNDO_COMPRESS_TYPE + */ + String DEFAULT_CLIENT_UNDO_COMPRESS_TYPE = "zip"; + + /** + * the constant DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD + */ + String DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD = "64k"; + + /** + * the constant DEFAULT_RETRY_DEAD_THRESHOLD + */ + int DEFAULT_RETRY_DEAD_THRESHOLD = 2 * 60 * 1000 + 10 * 1000; +} diff --git a/common/src/main/java/io/seata/common/XID.java b/common/src/main/java/io/seata/common/XID.java new file mode 100644 index 0000000..511de14 --- /dev/null +++ b/common/src/main/java/io/seata/common/XID.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common; + +/** + * The type Xid. + * + * @author slievrly + */ +public class XID { + + private static int port; + + private static String ipAddress; + + /** + * Sets port. + * + * @param port the port + */ + public static void setPort(int port) { + XID.port = port; + } + + /** + * Sets ip address. + * + * @param ipAddress the ip address + */ + public static void setIpAddress(String ipAddress) { + XID.ipAddress = ipAddress; + } + + /** + * Generate xid string. + * + * @param tranId the tran id + * @return the string + */ + public static String generateXID(long tranId) { + return ipAddress + ":" + port + ":" + tranId; + } + + /** + * Gets transaction id. + * + * @param xid the xid + * @return the transaction id + */ + public static long getTransactionId(String xid) { + if (xid == null) { + return -1; + } + + int idx = xid.lastIndexOf(":"); + return Long.parseLong(xid.substring(idx + 1)); + } + + /** + * Gets port. + * + * @return the port + */ + public static int getPort() { + return port; + } + + /** + * Gets ip address. + * + * @return the ip address + */ + public static String getIpAddress() { + return ipAddress; + } +} diff --git a/common/src/main/java/io/seata/common/exception/DataAccessException.java b/common/src/main/java/io/seata/common/exception/DataAccessException.java new file mode 100644 index 0000000..bed828c --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/DataAccessException.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * the data access exception + * @author jsbxyyx + */ +public class DataAccessException extends StoreException { + + /** + * default constructor + */ + public DataAccessException() { + } + + + /** + * constructor with framework error code + * @param err the framework error code + */ + public DataAccessException(FrameworkErrorCode err) { + super(err); + } + + /** + * constructor with msg + * @param msg the msg + */ + public DataAccessException(String msg) { + super(msg); + } + + /** + * constructor with cause + * @param cause the cause + */ + public DataAccessException(Throwable cause) { + super(cause); + } + + /** + * constructor with msg and framework error code + * @param msg the msg + * @param errCode the framework error code + */ + public DataAccessException(String msg, FrameworkErrorCode errCode) { + super(msg, errCode); + } + + /** + * constructor with cause and msg and framework error code + * @param cause the throwable + * @param msg the msg + * @param errCode the framework error code + */ + public DataAccessException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(cause, msg, errCode); + } + + +} diff --git a/common/src/main/java/io/seata/common/exception/EurekaRegistryException.java b/common/src/main/java/io/seata/common/exception/EurekaRegistryException.java new file mode 100644 index 0000000..dd65ff7 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/EurekaRegistryException.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * eureka registry exception + * + * @author: rui_849217@163.com + */ +public class EurekaRegistryException extends RuntimeException { + /** + * eureka registry exception. + */ + public EurekaRegistryException() { + super(); + } + + /** + * eureka registry exception. + * + * @param message the message + */ + public EurekaRegistryException(String message) { + super(message); + } + + /** + * eureka registry exception. + * + * @param message the message + * @param cause the cause + */ + public EurekaRegistryException(String message, Throwable cause) { + super(message, cause); + } + + /** + * eureka registry exception. + * + * @param cause the cause + */ + public EurekaRegistryException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/main/java/io/seata/common/exception/FrameworkErrorCode.java b/common/src/main/java/io/seata/common/exception/FrameworkErrorCode.java new file mode 100644 index 0000000..535769d --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/FrameworkErrorCode.java @@ -0,0 +1,257 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * The enum Framework error code. + * + * @author slievrly + */ +public enum FrameworkErrorCode { + /** + * 0001 ~ 0099 Configuration related errors + */ + ThreadPoolFull("0004", "Thread pool is full", "Please check the thread pool configuration"), + + /** + * The Init services client error. + */ + InitSeataClientError("0008", "Seata app name or seata server group is null", "Please check your configuration"), + + /** + * The Null rule error. + */ + NullRuleError("0010", "Services rules is null", "Please check your configuration"), + + /** + * 0101 ~ 0199 Network related error. (Not connected, disconnected, dispatched, etc.) + */ + NetConnect("0101", "Can not connect to the server", "Please check if the seata service is started. Is the network connection to the seata server normal?"), + + /** + * The Net reg appname. + */ + NetRegAppname("0102", "Register client app name failed", "Please check if the seata service is started. Is the network connection to the seata server normal?"), + + /** + * The Net disconnect. + */ + NetDisconnect("0103", "Seata connection closed", "The network is disconnected. Please check the network connection to the client or seata server."), + + /** + * The Net dispatch. + */ + NetDispatch("0104", "Dispatch error", "Network processing error. Please check the network connection to the client or seata server."), + + /** + * The Net on message. + */ + NetOnMessage("0105", "On message error", "Network processing error. Please check the network connection to the client or seata server."), + /** + * Get channel error framework error code. + */ + getChannelError("0106", "Get channel error", "Get channel error"), + + /** + * Channel not writable framework error code. + */ + ChannelNotWritable("0107", "Channel not writable", "Channel not writable"), + + /** + * Send half message failed framework error code. + */ + SendHalfMessageFailed("0108", "Send half message failed", "Send half message failed"), + + /** + * Channel is not writable framework error code. + */ + ChannelIsNotWritable("0109", "Channel is not writable", "Channel is not writable"), + /** + * No available service framework error code. + */ + NoAvailableService("0110", "No available service", "No available service"), + + /** + * Invalid configuration framework error code. + */ + InvalidConfiguration("0201", "Invalid configuration", "Invalid configuration"), + + /** + * Exception caught framework error code. + */ + ExceptionCaught("0318", "Exception caught", "Exception caught"), + + /** + * Register rm framework error code. + */ + RegisterRM("0304", "Register RM failed", "Register RM failed"), + + /** 0400~0499 Saga related error **/ + + /** + * Process type not found + */ + ProcessTypeNotFound("0401", "Process type not found", "Process type not found"), + + /** + * Process handler not found + */ + ProcessHandlerNotFound("0402", "Process handler not found", "Process handler not found"), + + /** + * Process router not found + */ + ProcessRouterNotFound("0403", "Process router not found", "Process router not found"), + + /** + * method not public + */ + MethodNotPublic("0404", "method not public", "method not public"), + + /** + * method invoke error + */ + MethodInvokeError("0405", "method invoke error", "method invoke error"), + + /** + * CompensationState not found + */ + CompensationStateNotFound("0406", "CompensationState not found", "CompensationState not found"), + + /** + * Evaluation returns null + */ + EvaluationReturnsNull("0407", "Evaluation returns null", "Evaluation returns null"), + + /** + * Evaluation returns non-Boolean + */ + EvaluationReturnsNonBoolean("0408", "Evaluation returns non-Boolean", "Evaluation returns non-Boolean"), + + /** + * Not a exception class + */ + NotExceptionClass("0409", "Not a exception class", "Not a exception class"), + + /** + * No such method + */ + NoSuchMethod("0410", "No such method", "No such method"), + + /** + * Object not exists + */ + ObjectNotExists("0411", "Object not exists", "Object not exists"), + + /** + * Parameter required + */ + ParameterRequired("0412", "Parameter required", "Parameter required"), + + /** + * Variables assign error + */ + VariablesAssignError("0413", "Variables assign error", "Variables assign error"), + + /** + * No matched status + */ + NoMatchedStatus("0414", "No matched status", "No matched status"), + + /** + * Asynchronous start disabled + */ + AsynchronousStartDisabled("0415", "Asynchronous start disabled", "Asynchronous start disabled"), + + /** + * Operation denied + */ + OperationDenied("0416", "Operation denied", "Operation denied"), + + /** + * Context variable replay failed + */ + ContextVariableReplayFailed("0417", "Context variable replay failed", "Context variable replay failed"), + + /** + * Context variable replay failed + */ + InvalidParameter("0418", "Invalid parameter", "Invalid parameter"), + + /** + * Invoke transaction manager error + */ + TransactionManagerError("0419", "Invoke transaction manager error", "Invoke transaction manager error"), + + /** + * State machine instance not exists + */ + StateMachineInstanceNotExists("0420", "State machine instance not exists", "State machine instance not exists"), + + /** + * State machine execution timeout + */ + StateMachineExecutionTimeout("0421", "State machine execution timeout", "State machine execution timeout"), + + /** + * State machine execution no choice matched + */ + StateMachineNoChoiceMatched("0422", "State machine no choice matched", "State machine no choice matched"), + + /** + * Undefined error + */ + UnknownAppError("10000", "Unknown error", "Internal error"), + ; + + /** + * The Err code. + */ + private String errCode; + + /** + * The Err message. + */ + private String errMessage; + + /** + * The Err dispose. + */ + private String errDispose; + + FrameworkErrorCode(String errCode, String errMessage, String errDispose) { + this.errCode = errCode; + this.errMessage = errMessage; + this.errDispose = errDispose; + } + + public String getErrCode() { + return errCode; + } + + public String getErrMessage() { + return errMessage; + } + + public String getErrDispose() { + return errDispose; + } + + @Override + public String toString() { + return String.format("[%s] [%s] [%s]", errCode, errMessage, errDispose); + } +} diff --git a/common/src/main/java/io/seata/common/exception/FrameworkException.java b/common/src/main/java/io/seata/common/exception/FrameworkException.java new file mode 100644 index 0000000..0866614 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/FrameworkException.java @@ -0,0 +1,161 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import java.sql.SQLException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Framework exception. + * + * @author slievrly + */ +public class FrameworkException extends RuntimeException { + private static final Logger LOGGER = LoggerFactory.getLogger(FrameworkException.class); + + private static final long serialVersionUID = 5531074229174745826L; + + private final FrameworkErrorCode errcode; + + /** + * Instantiates a new Framework exception. + */ + public FrameworkException() { + this(FrameworkErrorCode.UnknownAppError); + } + + /** + * Instantiates a new Framework exception. + * + * @param err the err + */ + public FrameworkException(FrameworkErrorCode err) { + this(err.getErrMessage(), err); + } + + /** + * Instantiates a new Framework exception. + * + * @param msg the msg + */ + public FrameworkException(String msg) { + this(msg, FrameworkErrorCode.UnknownAppError); + } + + /** + * Instantiates a new Framework exception. + * + * @param msg the msg + * @param errCode the err code + */ + public FrameworkException(String msg, FrameworkErrorCode errCode) { + this(null, msg, errCode); + } + + /** + * Instantiates a new Framework exception. + * + * @param cause the cause + * @param msg the msg + * @param errCode the err code + */ + public FrameworkException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(msg, cause); + this.errcode = errCode; + } + + /** + * Instantiates a new Framework exception. + * + * @param th the th + */ + public FrameworkException(Throwable th) { + this(th, th.getMessage()); + } + + /** + * Instantiates a new Framework exception. + * + * @param th the th + * @param msg the msg + */ + public FrameworkException(Throwable th, String msg) { + this(th, msg, FrameworkErrorCode.UnknownAppError); + } + + /** + * Gets errcode. + * + * @return the errcode + */ + public FrameworkErrorCode getErrcode() { + return errcode; + } + + /** + * Nested exception framework exception. + * + * @param e the e + * @return the framework exception + */ + public static FrameworkException nestedException(Throwable e) { + return nestedException("", e); + } + + /** + * Nested exception framework exception. + * + * @param msg the msg + * @param e the e + * @return the framework exception + */ + public static FrameworkException nestedException(String msg, Throwable e) { + LOGGER.error(msg, e.getMessage(), e); + if (e instanceof FrameworkException) { + return (FrameworkException)e; + } + + return new FrameworkException(e, msg); + } + + /** + * Nested sql exception sql exception. + * + * @param e the e + * @return the sql exception + */ + public static SQLException nestedSQLException(Throwable e) { + return nestedSQLException("", e); + } + + /** + * Nested sql exception sql exception. + * + * @param msg the msg + * @param e the e + * @return the sql exception + */ + public static SQLException nestedSQLException(String msg, Throwable e) { + LOGGER.error(msg, e.getMessage(), e); + if (e instanceof SQLException) { + return (SQLException)e; + } + + return new SQLException(e); + } +} diff --git a/common/src/main/java/io/seata/common/exception/NotSupportYetException.java b/common/src/main/java/io/seata/common/exception/NotSupportYetException.java new file mode 100644 index 0000000..d3f5611 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/NotSupportYetException.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * The type Not support yet exception. + * + * @author slievrly + */ +public class NotSupportYetException extends RuntimeException { + + /** + * Instantiates a new Not support yet exception. + */ + public NotSupportYetException() { + super(); + } + + /** + * Instantiates a new Not support yet exception. + * + * @param message the message + */ + public NotSupportYetException(String message) { + super(message); + } + + /** + * Instantiates a new Not support yet exception. + * + * @param message the message + * @param cause the cause + */ + public NotSupportYetException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Not support yet exception. + * + * @param cause the cause + */ + public NotSupportYetException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/main/java/io/seata/common/exception/RedisException.java b/common/src/main/java/io/seata/common/exception/RedisException.java new file mode 100644 index 0000000..650bae8 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/RedisException.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * The redis operate exception + * + * @author wangzhongxiang + */ +public class RedisException extends FrameworkException { + + /** + * Instantiates a new Redis exception. + */ + public RedisException() { + } + + /** + * Instantiates a new Redis exception. + * + * @param err the err + */ + public RedisException(FrameworkErrorCode err) { + super(err); + } + + /** + * Instantiates a new Redis exception. + * + * @param msg the msg + */ + public RedisException(String msg) { + super(msg); + } + + /** + * Instantiates a new Redis exception. + * + * @param msg the msg + * @param errCode the err code + */ + public RedisException(String msg, FrameworkErrorCode errCode) { + super(msg, errCode); + } + + /** + * Instantiates a new Redis exception. + * + * @param cause the cause + * @param msg the msg + * @param errCode the err code + */ + public RedisException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(cause, msg, errCode); + } + + /** + * Instantiates a new Redis exception. + * + * @param th the th + */ + public RedisException(Throwable th) { + super(th); + } + + /** + * Instantiates a new Redis exception. + * + * @param th the th + * @param msg the msg + */ + public RedisException(Throwable th, String msg) { + super(th, msg); + } +} diff --git a/common/src/main/java/io/seata/common/exception/ShouldNeverHappenException.java b/common/src/main/java/io/seata/common/exception/ShouldNeverHappenException.java new file mode 100644 index 0000000..f624f38 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/ShouldNeverHappenException.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * The type Should never happen exception. + * + * @author slievrly + */ +public class ShouldNeverHappenException extends RuntimeException { + + /** + * Instantiates a new Should never happen exception. + */ + public ShouldNeverHappenException() { + super(); + } + + /** + * Instantiates a new Should never happen exception. + * + * @param message the message + */ + public ShouldNeverHappenException(String message) { + super(message); + } + + /** + * Instantiates a new Should never happen exception. + * + * @param message the message + * @param cause the cause + */ + public ShouldNeverHappenException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Should never happen exception. + * + * @param cause the cause + */ + public ShouldNeverHappenException(Throwable cause) { + super(cause); + } +} diff --git a/common/src/main/java/io/seata/common/exception/StoreException.java b/common/src/main/java/io/seata/common/exception/StoreException.java new file mode 100644 index 0000000..efcd8a0 --- /dev/null +++ b/common/src/main/java/io/seata/common/exception/StoreException.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +/** + * the store exception + * + * @author zhangsen + */ +public class StoreException extends FrameworkException { + + /** + * Instantiates a new Store exception. + */ + public StoreException() { + } + + /** + * Instantiates a new Store exception. + * + * @param err the err + */ + public StoreException(FrameworkErrorCode err) { + super(err); + } + + /** + * Instantiates a new Store exception. + * + * @param msg the msg + */ + public StoreException(String msg) { + super(msg); + } + + /** + * Instantiates a new Store exception. + * + * @param msg the msg + * @param errCode the err code + */ + public StoreException(String msg, FrameworkErrorCode errCode) { + super(msg, errCode); + } + + /** + * Instantiates a new Store exception. + * + * @param cause the cause + * @param msg the msg + * @param errCode the err code + */ + public StoreException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(cause, msg, errCode); + } + + /** + * Instantiates a new Store exception. + * + * @param th the th + */ + public StoreException(Throwable th) { + super(th); + } + + /** + * Instantiates a new Store exception. + * + * @param th the th + * @param msg the msg + */ + public StoreException(Throwable th, String msg) { + super(th, msg); + } +} diff --git a/common/src/main/java/io/seata/common/executor/Callback.java b/common/src/main/java/io/seata/common/executor/Callback.java new file mode 100644 index 0000000..fc3ff53 --- /dev/null +++ b/common/src/main/java/io/seata/common/executor/Callback.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.executor; + +/** + * The interface Callback. + * + * @param the type parameter + * + * @author zhangsen + */ +public interface Callback { + + /** + * Execute t. + * + * @return the t + * @throws Throwable the throwable + */ + T execute() throws Throwable; +} + diff --git a/common/src/main/java/io/seata/common/executor/Initialize.java b/common/src/main/java/io/seata/common/executor/Initialize.java new file mode 100644 index 0000000..49c41cd --- /dev/null +++ b/common/src/main/java/io/seata/common/executor/Initialize.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.executor; + +/** + * The interface Initialize. + * + * @author zhangsen + */ +public interface Initialize { + + /** + * init method + */ + void init(); + +} diff --git a/common/src/main/java/io/seata/common/holder/ObjectHolder.java b/common/src/main/java/io/seata/common/holder/ObjectHolder.java new file mode 100644 index 0000000..ea92833 --- /dev/null +++ b/common/src/main/java/io/seata/common/holder/ObjectHolder.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.holder; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.ShouldNeverHappenException; + +/** + * @author xingfudeshi@gmail.com + * The enum object holder + */ +public enum ObjectHolder { + /** + * singleton instance + */ + INSTANCE; + private static final int MAP_SIZE = 8; + private static final Map OBJECT_MAP = new ConcurrentHashMap<>(MAP_SIZE); + + public Object getObject(String objectKey) { + return OBJECT_MAP.get(objectKey); + } + + public T getObject(Class clasz) { + return clasz.cast(OBJECT_MAP.values().stream().filter(clasz::isInstance).findAny().orElseThrow(() -> new ShouldNeverHappenException("Can't find any object of class " + clasz.getName()))); + } + + /** + * Sets object. + * + * @param objectKey the key + * @param object the object + * @return the previous object with the key, or null + */ + public Object setObject(String objectKey, Object object) { + return OBJECT_MAP.putIfAbsent(objectKey, object); + } +} diff --git a/common/src/main/java/io/seata/common/loader/EnhancedServiceLoader.java b/common/src/main/java/io/seata/common/loader/EnhancedServiceLoader.java new file mode 100644 index 0000000..2518e21 --- /dev/null +++ b/common/src/main/java/io/seata/common/loader/EnhancedServiceLoader.java @@ -0,0 +1,587 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; + +import io.seata.common.Constants; +import io.seata.common.executor.Initialize; +import io.seata.common.util.CollectionUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Enhanced service loader. + * + * @author slievrly + */ +public class EnhancedServiceLoader { + + /** + * Specify classLoader to load the service provider + * + * @param the type parameter + * @param service the service + * @param loader the loader + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service, ClassLoader loader) throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(loader); + } + + /** + * load service provider + * + * @param the type parameter + * @param service the service + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service) throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(findClassLoader()); + } + + /** + * load service provider + * + * @param the type parameter + * @param service the service + * @param activateName the activate name + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service, String activateName) throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, findClassLoader()); + } + + /** + * Specify classLoader to load the service provider + * + * @param the type parameter + * @param service the service + * @param activateName the activate name + * @param loader the loader + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service, String activateName, ClassLoader loader) + throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, loader); + } + + /** + * Load s. + * + * @param the type parameter + * @param service the service + * @param activateName the activate name + * @param args the args + * @return the s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service, String activateName, Object[] args) + throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, args, findClassLoader()); + } + + /** + * Load s. + * + * @param the type parameter + * @param service the service + * @param activateName the activate name + * @param argsType the args type + * @param args the args + * @return the s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static S load(Class service, String activateName, Class[] argsType, Object[] args) + throws EnhancedServiceNotFoundException { + return InnerEnhancedServiceLoader.getServiceLoader(service).load(activateName, argsType, args, findClassLoader()); + } + + /** + * get all implements + * + * @param the type parameter + * @param service the service + * @return list list + */ + public static List loadAll(Class service) { + return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(findClassLoader()); + } + + /** + * get all implements + * + * @param the type parameter + * @param service the service + * @param argsType the args type + * @param args the args + * @return list list + */ + public static List loadAll(Class service, Class[] argsType, Object[] args) { + return InnerEnhancedServiceLoader.getServiceLoader(service).loadAll(argsType, args, findClassLoader()); + } + + /** + * Get all the extension classes, follow {@linkplain LoadLevel} defined and sort order + * + * @param the type parameter + * @param service the service + * @return all extension class + */ + @SuppressWarnings("rawtypes") + static List getAllExtensionClass(Class service) { + return InnerEnhancedServiceLoader.getServiceLoader(service).getAllExtensionClass(findClassLoader()); + } + + /** + * Get all the extension classes, follow {@linkplain LoadLevel} defined and sort order + * + * @param the type parameter + * @param service the service + * @param loader the loader + * @return all extension class + */ + @SuppressWarnings("rawtypes") + static List getAllExtensionClass(Class service, ClassLoader loader) { + return InnerEnhancedServiceLoader.getServiceLoader(service).getAllExtensionClass(loader); + } + + /** + * Cannot use TCCL, in the pandora container will cause the class in the plugin not to be loaded + * + * @return + */ + private static ClassLoader findClassLoader() { + return EnhancedServiceLoader.class.getClassLoader(); + } + + + private static class InnerEnhancedServiceLoader { + private static final Logger LOGGER = LoggerFactory.getLogger(InnerEnhancedServiceLoader.class); + private static final String SERVICES_DIRECTORY = "META-INF/services/"; + private static final String SEATA_DIRECTORY = "META-INF/seata/"; + + private static final ConcurrentMap, InnerEnhancedServiceLoader> SERVICE_LOADERS = + new ConcurrentHashMap<>(); + + private final Class type; + private final Holder> definitionsHolder = new Holder<>(); + private final ConcurrentMap> definitionToInstanceMap = + new ConcurrentHashMap<>(); + private final ConcurrentMap> nameToDefinitionsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap, ExtensionDefinition> classToDefinitionMap = new ConcurrentHashMap<>(); + + private InnerEnhancedServiceLoader(Class type) { + this.type = type; + } + + /** + * Get the ServiceLoader for the specified Class + * + * @param type the type of the extension point + * @param the type + * @return the service loader + */ + private static InnerEnhancedServiceLoader getServiceLoader(Class type) { + if (type == null) { + throw new IllegalArgumentException("Enhanced Service type == null"); + } + return (InnerEnhancedServiceLoader)CollectionUtils.computeIfAbsent(SERVICE_LOADERS, type, + key -> new InnerEnhancedServiceLoader<>(type)); + } + + /** + * Specify classLoader to load the service provider + * + * @param loader the loader + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + private S load(ClassLoader loader) throws EnhancedServiceNotFoundException { + return loadExtension(loader, null, null); + } + + /** + * Specify classLoader to load the service provider + * + * @param activateName the activate name + * @param loader the loader + * @return s s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + private S load(String activateName, ClassLoader loader) + throws EnhancedServiceNotFoundException { + return loadExtension(activateName, loader, null, null); + } + + /** + * Load s. + * + * @param activateName the activate name + * @param args the args + * @param loader the loader + * @return the s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + private S load(String activateName, Object[] args, ClassLoader loader) + throws EnhancedServiceNotFoundException { + Class[] argsType = null; + if (args != null && args.length > 0) { + argsType = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + argsType[i] = args[i].getClass(); + } + } + return loadExtension(activateName, loader, argsType, args); + } + + /** + * Load s. + * + * @param activateName the activate name + * @param argsType the args type + * @param args the args + * @param loader the class loader + * @return the s + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + private S load(String activateName, Class[] argsType, Object[] args, ClassLoader loader) + throws EnhancedServiceNotFoundException { + return loadExtension(activateName, loader, argsType, args); + } + + /** + * get all implements + * @param loader the class loader + * + * @return list list + */ + private List loadAll(ClassLoader loader) { + return loadAll(null, null, loader); + } + + /** + * get all implements + * + * @param argsType the args type + * @param args the args + * @return list list + */ + private List loadAll(Class[] argsType, Object[] args, ClassLoader loader) { + List allInstances = new ArrayList<>(); + List allClazzs = getAllExtensionClass(loader); + if (CollectionUtils.isEmpty(allClazzs)) { + return allInstances; + } + try { + for (Class clazz : allClazzs) { + ExtensionDefinition definition = classToDefinitionMap.get(clazz); + allInstances.add(getExtensionInstance(definition, loader, argsType, args)); + } + } catch (Throwable t) { + throw new EnhancedServiceNotFoundException(t); + } + return allInstances; + } + + /** + * Get all the extension classes, follow {@linkplain LoadLevel} defined and sort order + * + * @param loader the loader + * @return all extension class + */ + @SuppressWarnings("rawtypes") + private List getAllExtensionClass(ClassLoader loader) { + return loadAllExtensionClass(loader); + } + + @SuppressWarnings("rawtypes") + private S loadExtension(ClassLoader loader, Class[] argTypes, + Object[] args) { + try { + loadAllExtensionClass(loader); + ExtensionDefinition defaultExtensionDefinition = getDefaultExtensionDefinition(); + return getExtensionInstance(defaultExtensionDefinition, loader, argTypes, args); + } catch (Throwable e) { + if (e instanceof EnhancedServiceNotFoundException) { + throw (EnhancedServiceNotFoundException)e; + } else { + throw new EnhancedServiceNotFoundException( + "not found service provider for : " + type.getName() + " caused by " + ExceptionUtils + .getFullStackTrace(e)); + } + } + } + + @SuppressWarnings("rawtypes") + private S loadExtension(String activateName, ClassLoader loader, Class[] argTypes, + Object[] args) { + if (io.seata.common.util.StringUtils.isEmpty(activateName)) { + throw new IllegalArgumentException("the name of service provider for [" + type.getName() + "] name is null"); + } + try { + loadAllExtensionClass(loader); + ExtensionDefinition cachedExtensionDefinition = getCachedExtensionDefinition(activateName); + return getExtensionInstance(cachedExtensionDefinition, loader, argTypes, args); + } catch (Throwable e) { + if (e instanceof EnhancedServiceNotFoundException) { + throw (EnhancedServiceNotFoundException)e; + } else { + throw new EnhancedServiceNotFoundException( + "not found service provider for : " + type.getName() + " caused by " + ExceptionUtils + .getFullStackTrace(e)); + } + } + } + + private S getExtensionInstance(ExtensionDefinition definition, ClassLoader loader, Class[] argTypes, + Object[] args) { + if (definition == null) { + throw new EnhancedServiceNotFoundException("not found service provider for : " + type.getName()); + } + if (Scope.SINGLETON.equals(definition.getScope())) { + Holder holder = CollectionUtils.computeIfAbsent(definitionToInstanceMap, definition, + key -> new Holder<>()); + Object instance = holder.get(); + if (instance == null) { + synchronized (holder) { + instance = holder.get(); + if (instance == null) { + instance = createNewExtension(definition, loader, argTypes, args); + holder.set(instance); + } + } + } + return (S)instance; + } else { + return createNewExtension(definition, loader, argTypes, args); + } + } + + private S createNewExtension(ExtensionDefinition definition, ClassLoader loader, Class[] argTypes, Object[] args) { + Class clazz = definition.getServiceClass(); + try { + S newInstance = initInstance(clazz, argTypes, args); + return newInstance; + } catch (Throwable t) { + throw new IllegalStateException("Extension instance(definition: " + definition + ", class: " + + type + ") could not be instantiated: " + t.getMessage(), t); + } + } + + private List loadAllExtensionClass(ClassLoader loader) { + List definitions = definitionsHolder.get(); + if (definitions == null) { + synchronized (definitionsHolder) { + definitions = definitionsHolder.get(); + if (definitions == null) { + definitions = findAllExtensionDefinition(loader); + definitionsHolder.set(definitions); + } + } + } + return definitions.stream().map(def -> def.getServiceClass()).collect(Collectors.toList()); + } + + @SuppressWarnings("rawtypes") + private List findAllExtensionDefinition(ClassLoader loader) { + List extensionDefinitions = new ArrayList<>(); + try { + loadFile(SERVICES_DIRECTORY, loader, extensionDefinitions); + loadFile(SEATA_DIRECTORY, loader, extensionDefinitions); + } catch (IOException e) { + throw new EnhancedServiceNotFoundException(e); + } + + //After loaded all the extensions,sort the caches by order + if (!nameToDefinitionsMap.isEmpty()) { + for (List definitions : nameToDefinitionsMap.values()) { + Collections.sort(definitions, (def1, def2) -> { + int o1 = def1.getOrder(); + int o2 = def2.getOrder(); + return Integer.compare(o1, o2); + }); + } + } + + if (!extensionDefinitions.isEmpty()) { + Collections.sort(extensionDefinitions, (definition1, definition2) -> { + int o1 = definition1.getOrder(); + int o2 = definition2.getOrder(); + return Integer.compare(o1, o2); + }); + } + + return extensionDefinitions; + } + + + @SuppressWarnings("rawtypes") + private void loadFile(String dir, ClassLoader loader, List extensions) + throws IOException { + String fileName = dir + type.getName(); + Enumeration urls; + if (loader != null) { + urls = loader.getResources(fileName); + } else { + urls = ClassLoader.getSystemResources(fileName); + } + if (urls != null) { + while (urls.hasMoreElements()) { + java.net.URL url = urls.nextElement(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), Constants.DEFAULT_CHARSET))) { + String line; + while ((line = reader.readLine()) != null) { + final int ci = line.indexOf('#'); + if (ci >= 0) { + line = line.substring(0, ci); + } + line = line.trim(); + if (line.length() > 0) { + try { + ExtensionDefinition extensionDefinition = getUnloadedExtensionDefinition(line, loader); + if (extensionDefinition == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("The same extension {} has already been loaded, skipped", line); + } + continue; + } + extensions.add(extensionDefinition); + } catch (LinkageError | ClassNotFoundException e) { + LOGGER.warn("Load [{}] class fail. {}", line, e.getMessage()); + } + } + } + } catch (Throwable e) { + LOGGER.warn("load clazz instance error: {}", e.getMessage()); + } + } + } + } + + private ExtensionDefinition getUnloadedExtensionDefinition(String className, ClassLoader loader) + throws ClassNotFoundException { + //Check whether the definition has been loaded + if (!isDefinitionContainsClazz(className, loader)) { + Class clazz = Class.forName(className, true, loader); + String serviceName = null; + Integer priority = 0; + Scope scope = Scope.SINGLETON; + LoadLevel loadLevel = clazz.getAnnotation(LoadLevel.class); + if (loadLevel != null) { + serviceName = loadLevel.name(); + priority = loadLevel.order(); + scope = loadLevel.scope(); + } + ExtensionDefinition result = new ExtensionDefinition(serviceName, priority, scope, clazz); + classToDefinitionMap.put(clazz, result); + if (serviceName != null) { + CollectionUtils.computeIfAbsent(nameToDefinitionsMap, serviceName, e -> new ArrayList<>()) + .add(result); + } + return result; + } + return null; + } + + private boolean isDefinitionContainsClazz(String className, ClassLoader loader) { + for (Map.Entry, ExtensionDefinition> entry : classToDefinitionMap.entrySet()) { + if (!entry.getKey().getName().equals(className)) { + continue; + } + if (Objects.equals(entry.getValue().getServiceClass().getClassLoader(), loader)) { + return true; + } + } + return false; + } + + private ExtensionDefinition getDefaultExtensionDefinition() { + List currentDefinitions = definitionsHolder.get(); + return CollectionUtils.getLast(currentDefinitions); + } + + private ExtensionDefinition getCachedExtensionDefinition(String activateName) { + List definitions = nameToDefinitionsMap.get(activateName); + return CollectionUtils.getLast(definitions); + } + + /** + * init instance + * + * @param implClazz the impl clazz + * @param argTypes the arg types + * @param args the args + * @return s s + * @throws IllegalAccessException the illegal access exception + * @throws InstantiationException the instantiation exception + * @throws NoSuchMethodException the no such method exception + * @throws InvocationTargetException the invocation target exception + */ + private S initInstance(Class implClazz, Class[] argTypes, Object[] args) + throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + S s = null; + if (argTypes != null && args != null) { + // Constructor with arguments + Constructor constructor = implClazz.getDeclaredConstructor(argTypes); + s = type.cast(constructor.newInstance(args)); + } else { + // default Constructor + s = type.cast(implClazz.newInstance()); + } + if (s instanceof Initialize) { + ((Initialize)s).init(); + } + return s; + } + + /** + * Helper Class for hold a value. + * @param + */ + private static class Holder { + private volatile T value; + + private void set(T value) { + this.value = value; + } + + private T get() { + return value; + } + } + } + + +} \ No newline at end of file diff --git a/common/src/main/java/io/seata/common/loader/EnhancedServiceNotFoundException.java b/common/src/main/java/io/seata/common/loader/EnhancedServiceNotFoundException.java new file mode 100644 index 0000000..379a1de --- /dev/null +++ b/common/src/main/java/io/seata/common/loader/EnhancedServiceNotFoundException.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +import org.apache.commons.lang.exception.NestableRuntimeException; + +/** + * The type Enhanced service not found exception. + * + * @author slievrly + */ +public class EnhancedServiceNotFoundException extends NestableRuntimeException { + private static final long serialVersionUID = 7748438218914409019L; + + /** + * Instantiates a new Enhanced service not found exception. + * + * @param errorCode the error code + */ + public EnhancedServiceNotFoundException(String errorCode) { + super(errorCode); + } + + /** + * Instantiates a new Enhanced service not found exception. + * + * @param errorCode the error code + * @param cause the cause + */ + public EnhancedServiceNotFoundException(String errorCode, Throwable cause) { + super(errorCode, cause); + } + + /** + * Instantiates a new Enhanced service not found exception. + * + * @param errorCode the error code + * @param errorDesc the error desc + */ + public EnhancedServiceNotFoundException(String errorCode, String errorDesc) { + super(errorCode + ":" + errorDesc); + } + + /** + * Instantiates a new Enhanced service not found exception. + * + * @param errorCode the error code + * @param errorDesc the error desc + * @param cause the cause + */ + public EnhancedServiceNotFoundException(String errorCode, String errorDesc, Throwable cause) { + super(errorCode + ":" + errorDesc, cause); + } + + /** + * Instantiates a new Enhanced service not found exception. + * + * @param cause the cause + */ + public EnhancedServiceNotFoundException(Throwable cause) { + super(cause); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/common/src/main/java/io/seata/common/loader/ExtensionDefinition.java b/common/src/main/java/io/seata/common/loader/ExtensionDefinition.java new file mode 100644 index 0000000..2f76e53 --- /dev/null +++ b/common/src/main/java/io/seata/common/loader/ExtensionDefinition.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +import io.seata.common.util.StringUtils; + +/** + * The type ExtensionDefinition + * + * @author haozhibei + */ +final class ExtensionDefinition { + private String name; + private Class serviceClass; + private Integer order; + private Scope scope; + + public Integer getOrder() { + return this.order; + } + + public Class getServiceClass() { + return this.serviceClass; + } + + public Scope getScope() { + return this.scope; + } + + public ExtensionDefinition(String name, Integer order, Scope scope, Class clazz) { + this.name = name; + this.order = order; + this.scope = scope; + this.serviceClass = clazz; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((serviceClass == null) ? 0 : serviceClass.hashCode()); + result = prime * result + ((order == null) ? 0 : order.hashCode()); + result = prime * result + ((scope == null) ? 0 : scope.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ExtensionDefinition other = (ExtensionDefinition)obj; + if (!StringUtils.equals(name, other.name)) { + return false; + } + if (!serviceClass.equals(other.serviceClass)) { + return false; + } + if (!order.equals(other.order)) { + return false; + } + return !scope.equals(other.scope); + } + + +} diff --git a/common/src/main/java/io/seata/common/loader/LoadLevel.java b/common/src/main/java/io/seata/common/loader/LoadLevel.java new file mode 100644 index 0000000..fe49ba7 --- /dev/null +++ b/common/src/main/java/io/seata/common/loader/LoadLevel.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The interface Load level. + * + * @author slievrly + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface LoadLevel { + /** + * Name string. + * + * @return the string + */ + String name(); + + /** + * Order int. + * + * @return the int + */ + int order() default 0; + + /** + * Scope enum. + * @return + */ + Scope scope() default Scope.SINGLETON; +} diff --git a/common/src/main/java/io/seata/common/loader/Scope.java b/common/src/main/java/io/seata/common/loader/Scope.java new file mode 100644 index 0000000..cb8d76a --- /dev/null +++ b/common/src/main/java/io/seata/common/loader/Scope.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * the scope of the extension + * + * @author haozhibei + */ +public enum Scope { + /** + * The extension will be loaded in singleton mode + */ + SINGLETON, + + /** + * The extension will be loaded in multi instance mode + */ + PROTOTYPE + +} diff --git a/common/src/main/java/io/seata/common/rpc/RpcStatus.java b/common/src/main/java/io/seata/common/rpc/RpcStatus.java new file mode 100644 index 0000000..350a364 --- /dev/null +++ b/common/src/main/java/io/seata/common/rpc/RpcStatus.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.rpc; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +/** + * The state statistics. + * + * @author ph3636 + */ +public class RpcStatus { + + private static final ConcurrentMap SERVICE_STATUS_MAP = new ConcurrentHashMap<>(); + private final AtomicLong active = new AtomicLong(); + private final LongAdder total = new LongAdder(); + + private RpcStatus() { + } + + /** + * get the RpcStatus of this service + * + * @param service the service + * @return RpcStatus + */ + public static RpcStatus getStatus(String service) { + return SERVICE_STATUS_MAP.computeIfAbsent(service, key -> new RpcStatus()); + } + + /** + * remove the RpcStatus of this service + * + * @param service the service + */ + public static void removeStatus(String service) { + SERVICE_STATUS_MAP.remove(service); + } + + /** + * begin count + * + * @param service the service + */ + public static void beginCount(String service) { + getStatus(service).active.incrementAndGet(); + } + + /** + * end count + * + * @param service the service + */ + public static void endCount(String service) { + RpcStatus rpcStatus = getStatus(service); + rpcStatus.active.decrementAndGet(); + rpcStatus.total.increment(); + } + + /** + * get active. + * + * @return active + */ + public long getActive() { + return active.get(); + } + + /** + * get total. + * + * @return total + */ + public long getTotal() { + return total.longValue(); + } +} diff --git a/common/src/main/java/io/seata/common/thread/NamedThreadFactory.java b/common/src/main/java/io/seata/common/thread/NamedThreadFactory.java new file mode 100644 index 0000000..d04ce31 --- /dev/null +++ b/common/src/main/java/io/seata/common/thread/NamedThreadFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +import io.netty.util.concurrent.FastThreadLocalThread; +import io.seata.common.util.CollectionUtils; + +/** + * The type Named thread factory. + * + * @author slievrly + * @author ggndnn + */ +public class NamedThreadFactory implements ThreadFactory { + private final static Map PREFIX_COUNTER = new ConcurrentHashMap<>(); + private final ThreadGroup group; + private final AtomicInteger counter = new AtomicInteger(0); + private final String prefix; + private final int totalSize; + private final boolean makeDaemons; + + /** + * Instantiates a new Named thread factory. + * + * @param prefix the prefix + * @param totalSize the total size + * @param makeDaemons the make daemons + */ + public NamedThreadFactory(String prefix, int totalSize, boolean makeDaemons) { + int prefixCounter = CollectionUtils.computeIfAbsent(PREFIX_COUNTER, prefix, key -> new AtomicInteger(0)) + .incrementAndGet(); + SecurityManager securityManager = System.getSecurityManager(); + group = (securityManager != null) ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup(); + this.prefix = prefix + "_" + prefixCounter; + this.makeDaemons = makeDaemons; + this.totalSize = totalSize; + } + + /** + * Instantiates a new Named thread factory. + * + * @param prefix the prefix + * @param makeDaemons the make daemons + */ + public NamedThreadFactory(String prefix, boolean makeDaemons) { + this(prefix, 0, makeDaemons); + } + + /** + * Instantiates a new Named thread factory. + * + * @param prefix the prefix + * @param totalSize the total size + */ + public NamedThreadFactory(String prefix, int totalSize) { + this(prefix, totalSize, true); + } + + @Override + public Thread newThread(Runnable r) { + String name = prefix + "_" + counter.incrementAndGet(); + if (totalSize > 1) { + name += "_" + totalSize; + } + Thread thread = new FastThreadLocalThread(group, r, name); + + thread.setDaemon(makeDaemons); + if (thread.getPriority() != Thread.NORM_PRIORITY) { + thread.setPriority(Thread.NORM_PRIORITY); + } + return thread; + } +} diff --git a/common/src/main/java/io/seata/common/thread/PositiveAtomicCounter.java b/common/src/main/java/io/seata/common/thread/PositiveAtomicCounter.java new file mode 100644 index 0000000..2db4ed9 --- /dev/null +++ b/common/src/main/java/io/seata/common/thread/PositiveAtomicCounter.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * positive atomic counter, begin with 0, ensure the number is positive. + * + * @author Geng Zhang + */ +public class PositiveAtomicCounter { + private static final int MASK = 0x7FFFFFFF; + private final AtomicInteger atom; + + public PositiveAtomicCounter() { + atom = new AtomicInteger(0); + } + + public final int incrementAndGet() { + return atom.incrementAndGet() & MASK; + } + + public final int getAndIncrement() { + return atom.getAndIncrement() & MASK; + } + + public int get() { + return atom.get() & MASK; + } + +} \ No newline at end of file diff --git a/common/src/main/java/io/seata/common/thread/RejectedPolicies.java b/common/src/main/java/io/seata/common/thread/RejectedPolicies.java new file mode 100644 index 0000000..158b8c8 --- /dev/null +++ b/common/src/main/java/io/seata/common/thread/RejectedPolicies.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; + +/** + * Policies for RejectedExecutionHandler + * + * @author guoyao + */ +public final class RejectedPolicies { + + /** + * when rejected happened ,add the new task and run the oldest task + * + * @return rejected execution handler + */ + public static RejectedExecutionHandler runsOldestTaskPolicy() { + return (r, executor) -> { + if (executor.isShutdown()) { + return; + } + BlockingQueue workQueue = executor.getQueue(); + Runnable firstWork = workQueue.poll(); + boolean newTaskAdd = workQueue.offer(r); + if (firstWork != null) { + firstWork.run(); + } + if (!newTaskAdd) { + executor.execute(r); + } + }; + } +} diff --git a/common/src/main/java/io/seata/common/util/BeanUtils.java b/common/src/main/java/io/seata/common/util/BeanUtils.java new file mode 100644 index 0000000..d4e6072 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/BeanUtils.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import io.seata.common.exception.NotSupportYetException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The bean utils + * + * @author wangzhongxiang + */ +public class BeanUtils { + + protected static final Logger LOGGER = LoggerFactory.getLogger(BeanUtils.class); + + public static String beanToString(Object o) { + if (o == null) { + return null; + } + + Field[] fields = o.getClass().getDeclaredFields(); + StringBuilder buffer = new StringBuilder(); + buffer.append("["); + for (Field field : fields) { + Object val = null; + try { + val = ReflectionUtil.getFieldValue(o, field.getName()); + } catch (NoSuchFieldException e) { + LOGGER.warn(e.getMessage(), e); + } catch (IllegalAccessException e) { + LOGGER.warn(e.getMessage(), e); + } + if (val != null) { + buffer.append(field.getName()).append("=").append(val).append(", "); + } + } + if (buffer.length() > 2) { + buffer.delete(buffer.length() - 2, buffer.length()); + } + buffer.append("]"); + return buffer.toString(); + } + + /** + * map to object + * + * @param map the map + * @param clazz the Object class + * @return the object + */ + public static Object mapToObject(Map map, Class clazz) { + if (CollectionUtils.isEmpty(map)) { + return null; + } + try { + Object instance = clazz.newInstance(); + Field[] fields = instance.getClass().getDeclaredFields(); + for (Field field : fields) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { + continue; + } + boolean accessible = field.isAccessible(); + field.setAccessible(true); + Class type = field.getType(); + if (type == Date.class) { + if (!StringUtils.isEmpty(map.get(field.getName()))) { + field.set(instance, new Date(Long.valueOf(map.get(field.getName())))); + } + } else if (type == Long.class) { + if (!StringUtils.isEmpty(map.get(field.getName()))) { + field.set(instance, Long.valueOf(map.get(field.getName()))); + } + } else if (type == Integer.class) { + if (!StringUtils.isEmpty(map.get(field.getName()))) { + field.set(instance, Integer.valueOf(map.get(field.getName()))); + } + } else if (type == Double.class) { + if (!StringUtils.isEmpty(map.get(field.getName()))) { + field.set(instance, Double.valueOf(map.get(field.getName()))); + } + } else if (type == String.class) { + if (!StringUtils.isEmpty(map.get(field.getName()))) { + field.set(instance, map.get(field.getName())); + } + } + field.setAccessible(accessible); + } + return instance; + } catch (IllegalAccessException e) { + throw new NotSupportYetException( + "map to " + clazz.toString() + " failed:" + e.getMessage(), e); + } catch (InstantiationException e) { + throw new NotSupportYetException( + "map to " + clazz.toString() + " failed:" + e.getMessage(), e); + } + } + + + /** + * object to map + * + * @param object the object + * @return the map + */ + public static Map objectToMap(Object object) { + if (object == null) { + return null; + } + Map map = new HashMap<>(16); + Field[] fields = object.getClass().getDeclaredFields(); + try { + for (Field field : fields) { + boolean accessible = field.isAccessible(); + field.setAccessible(true); + if (field.getType() == Date.class) { + Date date = (Date) field.get(object); + if (date != null) { + map.put(field.getName(), String.valueOf(date.getTime())); + } + } else { + map.put(field.getName(), + field.get(object) == null ? "" : field.get(object).toString()); + } + field.setAccessible(accessible); + } + } catch (IllegalAccessException e) { + throw new NotSupportYetException( + "object " + object.getClass().toString() + " to map failed:" + e.getMessage()); + } + return map; + } + +} diff --git a/common/src/main/java/io/seata/common/util/BlobUtils.java b/common/src/main/java/io/seata/common/util/BlobUtils.java new file mode 100644 index 0000000..1c598d2 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/BlobUtils.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.sql.Blob; + +import javax.sql.rowset.serial.SerialBlob; + +import io.seata.common.Constants; +import io.seata.common.exception.ShouldNeverHappenException; + +/** + * The type Blob utils. + * + * @author slievrly + * @author Geng Zhang + */ +public class BlobUtils { + + private BlobUtils() { + + } + + /** + * String 2 blob blob. + * + * @param str the str + * @return the blob + */ + public static Blob string2blob(String str) { + if (str == null) { + return null; + } + + try { + return new SerialBlob(str.getBytes(Constants.DEFAULT_CHARSET)); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } + + /** + * Blob 2 string string. + * + * @param blob the blob + * @return the string + */ + public static String blob2string(Blob blob) { + if (blob == null) { + return null; + } + + try { + return new String(blob.getBytes((long) 1, (int) blob.length()), Constants.DEFAULT_CHARSET); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } + + /** + * Byte array to blob + * + * @param bytes the byte array + * @return the blob + */ + public static Blob bytes2Blob(byte[] bytes) { + if (bytes == null) { + return null; + } + + try { + return new SerialBlob(bytes); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } + + /** + * Blob to byte array. + * + * @param blob the blob + * @return the byte array + */ + public static byte[] blob2Bytes(Blob blob) { + if (blob == null) { + return null; + } + + try { + return blob.getBytes((long) 1, (int) blob.length()); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } +} diff --git a/common/src/main/java/io/seata/common/util/CollectionUtils.java b/common/src/main/java/io/seata/common/util/CollectionUtils.java new file mode 100644 index 0000000..d783de5 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/CollectionUtils.java @@ -0,0 +1,280 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +/** + * The type Collection utils. + * + * @author zhangsen + * @author Geng Zhang + */ +public class CollectionUtils { + + private CollectionUtils() { + } + + /** + * Is empty boolean. + * + * @param col the col + * @return the boolean + */ + public static boolean isEmpty(Collection col) { + return !isNotEmpty(col); + } + + /** + * Is not empty boolean. + * + * @param col the col + * @return the boolean + */ + public static boolean isNotEmpty(Collection col) { + return col != null && !col.isEmpty(); + } + + /** + * Is empty boolean. + * + * @param array the array + * @return the boolean + */ + public static boolean isEmpty(Object[] array) { + return !isNotEmpty(array); + } + + /** + * Is not empty boolean. + * + * @param array the array + * @return the boolean + */ + public static boolean isNotEmpty(Object[] array) { + return array != null && array.length > 0; + } + + /** + * Is empty boolean. + * + * @param map the map + * @return the boolean + */ + public static boolean isEmpty(Map map) { + return !isNotEmpty(map); + } + + /** + * Is not empty boolean. + * + * @param map the map + * @return the boolean + */ + public static boolean isNotEmpty(Map map) { + return map != null && !map.isEmpty(); + } + + /** + * To string. + * + * @param col the col + * @return the string + */ + public static String toString(Collection col) { + if (isEmpty(col)) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (Object obj : col) { + sb.append(StringUtils.toString(obj)); + sb.append(","); + } + sb.deleteCharAt(sb.length() - 1); + sb.append("]"); + return sb.toString(); + } + + /** + * To string map + * + * @param param map + * @return the string map + */ + public static Map toStringMap(Map param) { + Map covertMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(param)) { + param.forEach((key, value) -> { + if (value != null) { + covertMap.put(key, StringUtils.toString(value)); + } + }); + } + return covertMap; + } + + /** + * Is size equals boolean. + * + * @param col0 the col 0 + * @param col1 the col 1 + * @return the boolean + */ + public static boolean isSizeEquals(Collection col0, Collection col1) { + if (col0 == null) { + return col1 == null; + } else { + if (col1 == null) { + return false; + } else { + return col0.size() == col1.size(); + } + } + } + + private static final String KV_SPLIT = "="; + + private static final String PAIR_SPLIT = "&"; + + /** + * Encode map to string + * + * @param map origin map + * @return String string + */ + public static String encodeMap(Map map) { + if (map == null) { + return null; + } + if (map.isEmpty()) { + return StringUtils.EMPTY; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + sb.append(entry.getKey()).append(KV_SPLIT).append(entry.getValue()).append(PAIR_SPLIT); + } + return sb.substring(0, sb.length() - 1); + } + + /** + * Decode string to map + * + * @param data data + * @return map map + */ + public static Map decodeMap(String data) { + if (data == null) { + return null; + } + Map map = new HashMap<>(); + if (StringUtils.isBlank(data)) { + return map; + } + String[] kvPairs = data.split(PAIR_SPLIT); + if (kvPairs.length == 0) { + return map; + } + for (String kvPair : kvPairs) { + if (StringUtils.isNullOrEmpty(kvPair)) { + continue; + } + String[] kvs = kvPair.split(KV_SPLIT); + if (kvs.length != 2) { + continue; + } + map.put(kvs[0], kvs[1]); + } + return map; + } + + /** + * Compute if absent. + * Use this method if you are frequently using the same key, + * because the get method has no lock. + * + * @param map the map + * @param key the key + * @param mappingFunction the mapping function + * @param the type of key + * @param the type of value + * @return the value + */ + public static V computeIfAbsent(Map map, K key, Function mappingFunction) { + V value = map.get(key); + if (value != null) { + return value; + } + return map.computeIfAbsent(key, mappingFunction); + } + + /** + * To upper list list. + * + * @param sourceList the source list + * @return the list + */ + public static List toUpperList(List sourceList) { + if (isEmpty(sourceList)) { + return sourceList; + } + List destList = new ArrayList<>(sourceList.size()); + for (String element : sourceList) { + if (element != null) { + destList.add(element.toUpperCase()); + } else { + destList.add(null); + } + } + return destList; + } + + /** + * Get the last item. + *

+ * 'IndexOutOfBoundsException' may be thrown, because the `list.size()` and `list.get(size - 1)` are not atomic. + * This method can avoid the 'IndexOutOfBoundsException' cause by concurrency. + *

+ * + * @param list the list + * @param the type of item + * @return the last item + */ + public static T getLast(List list) { + if (isEmpty(list)) { + return null; + } + + int size; + while (true) { + size = list.size(); + if (size == 0) { + return null; + } + + try { + return list.get(size - 1); + } catch (IndexOutOfBoundsException ex) { + // catch the exception and continue to retry + } + } + } +} diff --git a/common/src/main/java/io/seata/common/util/CompressUtil.java b/common/src/main/java/io/seata/common/util/CompressUtil.java new file mode 100644 index 0000000..1c5b42c --- /dev/null +++ b/common/src/main/java/io/seata/common/util/CompressUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * The type Compress util. + */ +public class CompressUtil { + + /** + * Compress byte [ ]. + * + * @param src the src + * @return the byte [ ] + * @throws IOException the io exception + */ + public static byte[] compress(final byte[] src) throws IOException { + byte[] result; + ByteArrayOutputStream bos = new ByteArrayOutputStream(src.length); + GZIPOutputStream gos = new GZIPOutputStream(bos); + try { + gos.write(src); + gos.finish(); + result = bos.toByteArray(); + } finally { + IOUtil.close(bos, gos); + } + return result; + } + + /** + * Uncompress byte [ ]. + * + * @param src the src + * @return the byte [ ] + * @throws IOException the io exception + */ + public static byte[] uncompress(final byte[] src) throws IOException { + byte[] result; + byte[] uncompressData = new byte[src.length]; + ByteArrayInputStream bis = new ByteArrayInputStream(src); + GZIPInputStream iis = new GZIPInputStream(bis); + ByteArrayOutputStream bos = new ByteArrayOutputStream(src.length); + + try { + while (true) { + int len = iis.read(uncompressData, 0, uncompressData.length); + if (len <= 0) { + break; + } + bos.write(uncompressData, 0, len); + } + bos.flush(); + result = bos.toByteArray(); + } finally { + IOUtil.close(bis, iis, bos); + } + return result; + } + + /** + * Is compress data boolean. + * + * @param bytes the bytes + * @return the boolean + */ + public static boolean isCompressData(byte[] bytes) { + if (bytes != null && bytes.length > 2) { + int header = ((bytes[0] & 0xff)) | (bytes[1] & 0xff) << 8; + return GZIPInputStream.GZIP_MAGIC == header; + } + return false; + } +} \ No newline at end of file diff --git a/common/src/main/java/io/seata/common/util/ConfigTools.java b/common/src/main/java/io/seata/common/util/ConfigTools.java new file mode 100644 index 0000000..b3b5b7a --- /dev/null +++ b/common/src/main/java/io/seata/common/util/ConfigTools.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Scanner; +import javax.crypto.Cipher; + +/** + * @author funkye + */ +public class ConfigTools { + + // generate key pair + public static KeyPair getKeyPair() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + return keyPair; + } + + // obtain the public key (Base64 encoding) + public static String getPublicKey(KeyPair keyPair) { + PublicKey publicKey = keyPair.getPublic(); + byte[] bytes = publicKey.getEncoded(); + return byte2Base64(bytes); + } + + // obtain the private key (Base64 encoding) + public static String getPrivateKey(KeyPair keyPair) { + PrivateKey privateKey = keyPair.getPrivate(); + byte[] bytes = privateKey.getEncoded(); + return byte2Base64(bytes); + } + + // convert Base64 encoded public key to PublicKey object + public static PublicKey string2PublicKey(String pubStr) throws Exception { + byte[] keyBytes = base642Byte(pubStr); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + return publicKey; + } + + // convert Base64 encoded private key to PrivateKey object + public static PrivateKey string2PrivateKey(String priStr) throws Exception { + byte[] keyBytes = base642Byte(priStr); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + + // public key encryption + public static String publicEncrypt(String content, String pubStr) throws Exception { + PublicKey publicKey = string2PublicKey(pubStr); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] bytes = cipher.doFinal(content.getBytes()); + return byte2Base64(bytes); + } + + // public key decryption + public static String publicDecrypt(String content, String pubStr) throws Exception { + PublicKey publicKey = string2PublicKey(pubStr); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] bytes = cipher.doFinal(base642Byte(content)); + return new String(bytes, StandardCharsets.UTF_8); + } + + // private key encryption + public static String privateEncrypt(String content, String priStr) throws Exception { + PrivateKey privateKey = string2PrivateKey(priStr); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] bytes = cipher.doFinal(content.getBytes()); + return byte2Base64(bytes); + } + + // private key decryption + public static String privateDecrypt(String content, String priStr) throws Exception { + PrivateKey privateKey = string2PrivateKey(priStr); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] bytes = cipher.doFinal(base642Byte(content)); + return new String(bytes, StandardCharsets.UTF_8); + } + + // byte array to Base64 encoding + public static String byte2Base64(byte[] bytes) { + return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8); + } + + // Base64 encoding to byte array + public static byte[] base642Byte(String base64Key) { + return Base64.getDecoder().decode(base64Key); + } + + public static void main(String[] args) throws Exception { + Scanner scan = new Scanner(System.in); + KeyPair keyPair = getKeyPair(); + String publicKeyStr = ConfigTools.getPublicKey(keyPair); + String privateKeyStr = ConfigTools.getPrivateKey(keyPair); + System.out.println("publicKeyStr:\n" + publicKeyStr); + System.out.println("privateKeyStr:\n" + privateKeyStr); + System.out.println( + "after the key is generated, please keep your key pair properly, if you need to encrypt, please enter your database password"); + System.out.println("input 'q' exit"); + while (scan.hasNextLine()) { + String password = scan.nextLine(); + if (StringUtils.isNotBlank(password) && !"q".equalsIgnoreCase(password)) { + String byte2Base64 = ConfigTools.privateEncrypt(password, privateKeyStr); + System.out.println("encryption completed: \n" + byte2Base64); + } + break; + } + scan.close(); + } + +} diff --git a/common/src/main/java/io/seata/common/util/DurationUtil.java b/common/src/main/java/io/seata/common/util/DurationUtil.java new file mode 100644 index 0000000..d0019c0 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/DurationUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.time.Duration; + +/** + * @author XCXCXCXCX + */ +public class DurationUtil { + + public static final Duration DEFAULT_DURATION = Duration.ofMillis(-1); + + public static final String DAY_UNIT = "d"; + public static final String HOUR_UNIT = "h"; + public static final String MINUTE_UNIT = "m"; + public static final String SECOND_UNIT = "s"; + public static final String MILLIS_SECOND_UNIT = "ms"; + + public static Duration parse(String str) { + if (StringUtils.isBlank(str)) { + return DEFAULT_DURATION; + } + + if (str.contains(MILLIS_SECOND_UNIT)) { + Long value = doParse(MILLIS_SECOND_UNIT, str); + return value == null ? null : Duration.ofMillis(value); + } else if (str.contains(DAY_UNIT)) { + Long value = doParse(DAY_UNIT, str); + return value == null ? null : Duration.ofDays(value); + } else if (str.contains(HOUR_UNIT)) { + Long value = doParse(HOUR_UNIT, str); + return value == null ? null : Duration.ofHours(value); + } else if (str.contains(MINUTE_UNIT)) { + Long value = doParse(MINUTE_UNIT, str); + return value == null ? null : Duration.ofMinutes(value); + } else if (str.contains(SECOND_UNIT)) { + Long value = doParse(SECOND_UNIT, str); + return value == null ? null : Duration.ofSeconds(value); + } + try { + int millis = Integer.parseInt(str); + return Duration.ofMillis(millis); + } catch (Exception e) { + throw new UnsupportedOperationException(str + " can't parse to duration", e); + } + } + + private static Long doParse(String unit, String str) { + str = str.replace(unit, ""); + if ("".equals(str)) { + return null; + } + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + throw new UnsupportedOperationException("\"" + str + "\" can't parse to Duration", e); + } + } + +} diff --git a/common/src/main/java/io/seata/common/util/IOUtil.java b/common/src/main/java/io/seata/common/util/IOUtil.java new file mode 100644 index 0000000..0435529 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/IOUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +/** + * @author jsbxyyx + */ +public class IOUtil { + + /** + * close Closeable + * @param closeables the closeables + */ + public static void close(AutoCloseable... closeables) { + if (CollectionUtils.isNotEmpty(closeables)) { + for (AutoCloseable closeable : closeables) { + close(closeable); + } + } + } + + /** + * close Closeable + * @param closeable the closeable + */ + public static void close(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignore) { + } + } + } + +} diff --git a/common/src/main/java/io/seata/common/util/IdWorker.java b/common/src/main/java/io/seata/common/util/IdWorker.java new file mode 100644 index 0000000..b7e8013 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/IdWorker.java @@ -0,0 +1,187 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.net.NetworkInterface; +import java.util.Enumeration; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author funkye + * @author selfishlover + */ +public class IdWorker { + + /** + * Start time cut (2020-05-03) + */ + private final long twepoch = 1588435200000L; + + /** + * The number of bits occupied by workerId + */ + private final int workerIdBits = 10; + + /** + * The number of bits occupied by timestamp + */ + private final int timestampBits = 41; + + /** + * The number of bits occupied by sequence + */ + private final int sequenceBits = 12; + + /** + * Maximum supported machine id, the result is 1023 + */ + private final int maxWorkerId = ~(-1 << workerIdBits); + + /** + * business meaning: machine ID (0 ~ 1023) + * actual layout in memory: + * highest 1 bit: 0 + * middle 10 bit: workerId + * lowest 53 bit: all 0 + */ + private long workerId; + + /** + * timestamp and sequence mix in one Long + * highest 11 bit: not used + * middle 41 bit: timestamp + * lowest 12 bit: sequence + */ + private AtomicLong timestampAndSequence; + + /** + * mask that help to extract timestamp and sequence from a long + */ + private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits)); + + /** + * instantiate an IdWorker using given workerId + * @param workerId if null, then will auto assign one + */ + public IdWorker(Long workerId) { + initTimestampAndSequence(); + initWorkerId(workerId); + } + + /** + * init first timestamp and sequence immediately + */ + private void initTimestampAndSequence() { + long timestamp = getNewestTimestamp(); + long timestampWithSequence = timestamp << sequenceBits; + this.timestampAndSequence = new AtomicLong(timestampWithSequence); + } + + /** + * init workerId + * @param workerId if null, then auto generate one + */ + private void initWorkerId(Long workerId) { + if (workerId == null) { + workerId = generateWorkerId(); + } + if (workerId > maxWorkerId || workerId < 0) { + String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId); + throw new IllegalArgumentException(message); + } + this.workerId = workerId << (timestampBits + sequenceBits); + } + + /** + * get next UUID(base on snowflake algorithm), which look like: + * highest 1 bit: always 0 + * next 10 bit: workerId + * next 41 bit: timestamp + * lowest 12 bit: sequence + * @return UUID + */ + public long nextId() { + waitIfNecessary(); + long next = timestampAndSequence.incrementAndGet(); + long timestampWithSequence = next & timestampAndSequenceMask; + return workerId | timestampWithSequence; + } + + /** + * block current thread if the QPS of acquiring UUID is too high + * that current sequence space is exhausted + */ + private void waitIfNecessary() { + long currentWithSequence = timestampAndSequence.get(); + long current = currentWithSequence >>> sequenceBits; + long newest = getNewestTimestamp(); + if (current >= newest) { + try { + Thread.sleep(5); + } catch (InterruptedException ignore) { + // don't care + } + } + } + + /** + * get newest timestamp relative to twepoch + */ + private long getNewestTimestamp() { + return System.currentTimeMillis() - twepoch; + } + + /** + * auto generate workerId, try using mac first, if failed, then randomly generate one + * @return workerId + */ + private long generateWorkerId() { + try { + return generateWorkerIdBaseOnMac(); + } catch (Exception e) { + return generateRandomWorkerId(); + } + } + + /** + * use lowest 10 bit of available MAC as workerId + * @return workerId + * @throws Exception when there is no available mac found + */ + private long generateWorkerIdBaseOnMac() throws Exception { + Enumeration all = NetworkInterface.getNetworkInterfaces(); + while (all.hasMoreElements()) { + NetworkInterface networkInterface = all.nextElement(); + boolean isLoopback = networkInterface.isLoopback(); + boolean isVirtual = networkInterface.isVirtual(); + if (isLoopback || isVirtual) { + continue; + } + byte[] mac = networkInterface.getHardwareAddress(); + return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF); + } + throw new RuntimeException("no available mac found"); + } + + /** + * randomly generate one as workerId + * @return workerId + */ + private long generateRandomWorkerId() { + return new Random().nextInt(maxWorkerId + 1); + } +} diff --git a/common/src/main/java/io/seata/common/util/LambdaUtils.java b/common/src/main/java/io/seata/common/util/LambdaUtils.java new file mode 100644 index 0000000..134840e --- /dev/null +++ b/common/src/main/java/io/seata/common/util/LambdaUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * The type Lambda util. + * + * @author zjinlei + */ +public class LambdaUtils { + + public static Predicate distinctByKey(Function keyExtractor) { + Map seen = new ConcurrentHashMap<>(); + return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null; + } + +} diff --git a/common/src/main/java/io/seata/common/util/NetUtil.java b/common/src/main/java/io/seata/common/util/NetUtil.java new file mode 100644 index 0000000..3045de4 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/NetUtil.java @@ -0,0 +1,250 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Net util. + * + * @author slievrly + */ +public class NetUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(NetUtil.class); + private static final String LOCALHOST = "127.0.0.1"; + + private static final String ANY_HOST = "0.0.0.0"; + + private static volatile InetAddress LOCAL_ADDRESS = null; + + private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$"); + + /** + * To string address string. + * + * @param address the address + * @return the string + */ + public static String toStringAddress(SocketAddress address) { + if (address == null) { + return StringUtils.EMPTY; + } + return toStringAddress((InetSocketAddress) address); + } + + /** + * To ip address string. + * + * @param address the address + * @return the string + */ + public static String toIpAddress(SocketAddress address) { + InetSocketAddress inetSocketAddress = (InetSocketAddress) address; + return inetSocketAddress.getAddress().getHostAddress(); + } + + /** + * To string address string. + * + * @param address the address + * @return the string + */ + public static String toStringAddress(InetSocketAddress address) { + return address.getAddress().getHostAddress() + ":" + address.getPort(); + } + + /** + * To inet socket address inet socket address. + * + * @param address the address + * @return the inet socket address + */ + public static InetSocketAddress toInetSocketAddress(String address) { + int i = address.indexOf(':'); + String host; + int port; + if (i > -1) { + host = address.substring(0, i); + port = Integer.parseInt(address.substring(i + 1)); + } else { + host = address; + port = 0; + } + return new InetSocketAddress(host, port); + } + + /** + * To long long. + * + * @param address the address + * @return the long + */ + public static long toLong(String address) { + InetSocketAddress ad = toInetSocketAddress(address); + String[] ip = ad.getAddress().getHostAddress().split("\\."); + long r = 0; + r = r | (Long.parseLong(ip[0]) << 40); + r = r | (Long.parseLong(ip[1]) << 32); + r = r | (Long.parseLong(ip[2]) << 24); + r = r | (Long.parseLong(ip[3]) << 16); + r = r | ad.getPort(); + return r; + } + + /** + * Gets local ip. + * + * @return the local ip + */ + public static String getLocalIp() { + InetAddress address = getLocalAddress(); + return address == null ? LOCALHOST : address.getHostAddress(); + } + + /** + * Gets local host. + * + * @return the local host + */ + public static String getLocalHost() { + InetAddress address = getLocalAddress(); + return address == null ? "localhost" : address.getHostName(); + } + + /** + * Gets local address. + * + * @return the local address + */ + public static InetAddress getLocalAddress() { + if (LOCAL_ADDRESS != null) { + return LOCAL_ADDRESS; + } + InetAddress localAddress = getLocalAddress0(); + LOCAL_ADDRESS = localAddress; + return localAddress; + } + + private static InetAddress getLocalAddress0() { + InetAddress localAddress = null; + try { + localAddress = InetAddress.getLocalHost(); + if (isValidAddress(localAddress)) { + return localAddress; + } + } catch (Throwable e) { + LOGGER.warn("Failed to retrieving ip address, {}", e.getMessage(), e); + } + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + if (interfaces != null) { + while (interfaces.hasMoreElements()) { + try { + NetworkInterface network = interfaces.nextElement(); + Enumeration addresses = network.getInetAddresses(); + while (addresses.hasMoreElements()) { + try { + InetAddress address = addresses.nextElement(); + if (isValidAddress(address)) { + return address; + } + } catch (Throwable e) { + LOGGER.warn("Failed to retrieving ip address, {}", e.getMessage(), e); + } + } + } catch (Throwable e) { + LOGGER.warn("Failed to retrieving ip address, {}", e.getMessage(), e); + } + } + } + } catch (Throwable e) { + LOGGER.warn("Failed to retrieving ip address, {}", e.getMessage(), e); + } + LOGGER.error("Could not get local host ip address, will use 127.0.0.1 instead."); + return localAddress; + } + + /** + * Valid address. + * + * @param address the address + */ + public static void validAddress(InetSocketAddress address) { + if (address.getHostName() == null || 0 == address.getPort()) { + throw new IllegalArgumentException("invalid address:" + address); + } + } + + /** + * is valid address + * + * @param address + * @return true if the given address is valid + */ + private static boolean isValidAddress(InetAddress address) { + if (address == null || address.isLoopbackAddress()) { + return false; + } + return isValidIp(address.getHostAddress(), false); + } + + /** + * is valid IP + * + * @param ip + * @param validLocalAndAny Are 127.0.0.1 and 0.0.0.0 valid IPs? + * @return true if the given IP is valid + */ + public static boolean isValidIp(String ip, boolean validLocalAndAny) { + if (ip == null) { + return false; + } + ip = convertIpIfNecessary(ip); + if (validLocalAndAny) { + return IP_PATTERN.matcher(ip).matches(); + } else { + return !ANY_HOST.equals(ip) && !LOCALHOST.equals(ip) && IP_PATTERN.matcher(ip).matches(); + } + + } + + /** + * convert ip if necessary + * + * @param ip + * @return java.lang.String + */ + private static String convertIpIfNecessary(String ip) { + if (IP_PATTERN.matcher(ip).matches()) { + return ip; + } else { + try { + return InetAddress.getByName(ip).getHostAddress(); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/common/src/main/java/io/seata/common/util/NumberUtils.java b/common/src/main/java/io/seata/common/util/NumberUtils.java new file mode 100644 index 0000000..0724ef5 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/NumberUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +/** + * Number utility + * + * @author helloworlde + */ +public class NumberUtils { + + /** + *

Convert a String to an int, returning a + * default value if the conversion fails.

+ * + *

If the string is null, the default value is returned.

+ * + * @param str the string to convert, may be null + * @param defaultValue the default value + * @return the int represented by the string, or the default if conversion fails + */ + public static int toInt(final String str, final int defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Integer.parseInt(str); + } catch (NumberFormatException nfe) { + return defaultValue; + } + } + + public static Long toLong(String str, final Long defaultValue) { + if (str == null) { + return defaultValue; + } + try { + return Long.valueOf(str); + } catch (NumberFormatException nfe) { + return defaultValue; + } + } + + public static Long toLong(String str) { + if (StringUtils.isNotBlank(str)) { + return Long.valueOf(str); + } + return null; + } +} diff --git a/common/src/main/java/io/seata/common/util/ReflectionUtil.java b/common/src/main/java/io/seata/common/util/ReflectionUtil.java new file mode 100644 index 0000000..e76254b --- /dev/null +++ b/common/src/main/java/io/seata/common/util/ReflectionUtil.java @@ -0,0 +1,211 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Reflection tools + * + * @author zhangsen + */ +public class ReflectionUtil { + + /** + * The constant MAX_NEST_DEPTH. + */ + public static final int MAX_NEST_DEPTH = 20; + + /** + * Gets class by name. + * + * @param className the class name + * @return the class by name + * @throws ClassNotFoundException the class not found exception + */ + public static Class getClassByName(String className) throws ClassNotFoundException { + return Class.forName(className, true, Thread.currentThread().getContextClassLoader()); + } + + /** + * get Field Value + * + * @param target the target + * @param fieldName the field name + * @return field value + * @throws NoSuchFieldException the no such field exception + * @throws SecurityException the security exception + * @throws IllegalArgumentException the illegal argument exception + * @throws IllegalAccessException the illegal access exception + */ + public static Object getFieldValue(Object target, String fieldName) + throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + Class cl = target.getClass(); + int i = 0; + while ((i++) < MAX_NEST_DEPTH && cl != null) { + try { + Field field = cl.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (Exception e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchFieldException("class:" + target.getClass() + ", field:" + fieldName); + } + + /** + * invoke Method + * + * @param target the target + * @param methodName the method name + * @return object + * @throws NoSuchMethodException the no such method exception + * @throws SecurityException the security exception + * @throws IllegalAccessException the illegal access exception + * @throws IllegalArgumentException the illegal argument exception + * @throws InvocationTargetException the invocation target exception + */ + public static Object invokeMethod(Object target, String methodName) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + Class cl = target.getClass(); + int i = 0; + while ((i++) < MAX_NEST_DEPTH && cl != null) { + try { + Method m = cl.getDeclaredMethod(methodName); + m.setAccessible(true); + return m.invoke(target); + } catch (Exception e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchMethodException("class:" + target.getClass() + ", methodName:" + methodName); + } + + /** + * invoke Method + * + * @param target the target + * @param methodName the method name + * @param parameterTypes the parameter types + * @param args the args + * @return object + * @throws NoSuchMethodException the no such method exception + * @throws SecurityException the security exception + * @throws IllegalAccessException the illegal access exception + * @throws IllegalArgumentException the illegal argument exception + * @throws InvocationTargetException the invocation target exception + */ + public static Object invokeMethod(Object target, String methodName, Class[] parameterTypes, Object[] args) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + Class cl = target.getClass(); + int i = 0; + while ((i++) < MAX_NEST_DEPTH && cl != null) { + try { + Method m = cl.getDeclaredMethod(methodName, parameterTypes); + m.setAccessible(true); + return m.invoke(target, args); + } catch (Exception e) { + cl = cl.getSuperclass(); + } + } + throw new NoSuchMethodException("class:" + target.getClass() + ", methodName:" + methodName); + } + + /** + * invoke static Method + * + * @param targetClass the target class + * @param methodName the method name + * @param parameterTypes the parameter types + * @param parameterValues the parameter values + * @return object + * @throws NoSuchMethodException the no such method exception + * @throws SecurityException the security exception + * @throws IllegalAccessException the illegal access exception + * @throws IllegalArgumentException the illegal argument exception + * @throws InvocationTargetException the invocation target exception + */ + public static Object invokeStaticMethod(Class targetClass, String methodName, Class[] parameterTypes, + Object[] parameterValues) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException { + int i = 0; + while ((i++) < MAX_NEST_DEPTH && targetClass != null) { + try { + Method m = targetClass.getMethod(methodName, parameterTypes); + return m.invoke(null, parameterValues); + } catch (Exception e) { + targetClass = targetClass.getSuperclass(); + } + } + throw new NoSuchMethodException("class:" + targetClass + ", methodName:" + methodName); + } + + /** + * get Method by name + * + * @param classType the class type + * @param methodName the method name + * @param parameterTypes the parameter types + * @return method + * @throws NoSuchMethodException the no such method exception + * @throws SecurityException the security exception + */ + public static Method getMethod(Class classType, String methodName, Class[] parameterTypes) + throws NoSuchMethodException, SecurityException { + return classType.getMethod(methodName, parameterTypes); + } + + /** + * get all interface of the clazz + * + * @param clazz the clazz + * @return set + */ + public static Set> getInterfaces(Class clazz) { + if (clazz.isInterface()) { + return Collections.singleton(clazz); + } + Set> interfaces = new LinkedHashSet<>(); + while (clazz != null) { + Class[] ifcs = clazz.getInterfaces(); + for (Class ifc : ifcs) { + interfaces.addAll(getInterfaces(ifc)); + } + clazz = clazz.getSuperclass(); + } + return interfaces; + } + + public static void modifyStaticFinalField(Class cla, String modifyFieldName, Object newValue) + throws NoSuchFieldException, IllegalAccessException { + Field field = cla.getDeclaredField(modifyFieldName); + field.setAccessible(true); + Field modifiers = field.getClass().getDeclaredField("modifiers"); + modifiers.setAccessible(true); + modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(cla, newValue); + } +} diff --git a/common/src/main/java/io/seata/common/util/SizeUtil.java b/common/src/main/java/io/seata/common/util/SizeUtil.java new file mode 100644 index 0000000..0db0423 --- /dev/null +++ b/common/src/main/java/io/seata/common/util/SizeUtil.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +/** + * @author chd + */ +public class SizeUtil { + private static final long RADIX = 1024; + /** + * case size to byte length + * example: + * 2k => 2 * 1024 + * 2m => 2 * 1024 * 1024 + * 2g => 2 * 1024 * 1024 * 1024 + * 2t => 2 * 1024 * 1024 * 1024 * 1024 + * @param size the string size with unit + * @return the byte length + */ + public static long size2Long(String size) { + if (null == size || size.length() <= 1) { + throw new IllegalArgumentException("could not convert '" + size + "' to byte length"); + } + + String size2Lower = size.toLowerCase(); + char unit = size2Lower.charAt(size.length() - 1); + long number; + try { + number = NumberUtils.toLong(size2Lower.substring(0, size.length() - 1)); + } catch (NumberFormatException | NullPointerException ex) { + throw new IllegalArgumentException("could not convert '" + size + "' to byte length"); + } + + switch (unit) { + case 'k': + return number * RADIX; + case 'm': + return number * RADIX * RADIX; + case 'g': + return number * RADIX * RADIX * RADIX; + case 't': + return number * RADIX * RADIX * RADIX * RADIX; + default: + throw new IllegalArgumentException("could not convert '" + size + "' to byte length"); + } + } +} diff --git a/common/src/main/java/io/seata/common/util/StringFormatUtils.java b/common/src/main/java/io/seata/common/util/StringFormatUtils.java new file mode 100644 index 0000000..5f8d96b --- /dev/null +++ b/common/src/main/java/io/seata/common/util/StringFormatUtils.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +/** + * @author xingfudeshi@gmail.com + */ +public class StringFormatUtils { + private static final char MINUS = '-'; + private static final char UNDERLINE = '_'; + public static final char DOT = '.'; + + /** + * camelTo underline format + * + * @param param + * @return formatted string + */ + public static String camelToUnderline(String param) { + if (param == null || "".equals(param.trim())) { + return ""; + } + int len = param.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = param.charAt(i); + if (Character.isUpperCase(c)) { + sb.append(UNDERLINE); + sb.append(Character.toLowerCase(c)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * underline to camel + * + * @param param + * @return formatted string + */ + public static String underlineToCamel(String param) { + return formatCamel(param, UNDERLINE); + } + + /** + * minus to camel + * + * @param param + * @return formatted string + */ + public static String minusToCamel(String param) { + return formatCamel(param, MINUS); + } + + /** + * dot to camel + * + * @param param + * @return formatted string + */ + public static String dotToCamel(String param) { + return formatCamel(param, DOT); + } + + /** + * format camel + * + * @param param + * @param sign + * @return formatted string + */ + private static String formatCamel(String param, char sign) { + if (param == null || "".equals(param.trim())) { + return ""; + } + int len = param.length(); + StringBuilder sb = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = param.charAt(i); + if (c == sign) { + if (++i < len) { + sb.append(Character.toUpperCase(param.charAt(i))); + } + } else { + sb.append(c); + } + } + return sb.toString(); + } + + +} diff --git a/common/src/main/java/io/seata/common/util/StringUtils.java b/common/src/main/java/io/seata/common/util/StringUtils.java new file mode 100644 index 0000000..6bf534d --- /dev/null +++ b/common/src/main/java/io/seata/common/util/StringUtils.java @@ -0,0 +1,265 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import io.seata.common.Constants; +import io.seata.common.exception.ShouldNeverHappenException; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +/** + * The type String utils. + * + * @author slievrly + * @author Geng Zhang + */ +public class StringUtils { + + private StringUtils() { + } + + /** + * empty string + */ + public static final String EMPTY = ""; + + /** + * Is empty boolean. + * + * @param str the str + * @return the boolean + */ + public static boolean isNullOrEmpty(String str) { + return (str == null) || (str.isEmpty()); + } + + /** + * Is blank string ? + * + * @param str the str + * @return boolean boolean + */ + public static boolean isBlank(String str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return true; + } + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Is Not blank string ? + * + * @param str the str + * @return boolean boolean + */ + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + /** + * Equals boolean. + * + * @param a the a + * @param b the b + * @return boolean + */ + public static boolean equals(String a, String b) { + if (a == null) { + return b == null; + } + return a.equals(b); + } + + /** + * Equals ignore case boolean. + * + * @param a the a + * @param b the b + * @return the boolean + */ + public static boolean equalsIgnoreCase(String a, String b) { + if (a == null) { + return b == null; + } + return a.equalsIgnoreCase(b); + } + + /** + * Input stream 2 string string. + * + * @param is the is + * @return the string + */ + public static String inputStream2String(InputStream is) { + if (is == null) { + return null; + } + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int i; + while ((i = is.read()) != -1) { + baos.write(i); + } + return baos.toString(Constants.DEFAULT_CHARSET_NAME); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } + + /** + * Input stream to byte array + * + * @param is the is + * @return the byte array + */ + public static byte[] inputStream2Bytes(InputStream is) { + if (is == null) { + return null; + } + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int i; + while ((i = is.read()) != -1) { + baos.write(i); + } + return baos.toByteArray(); + } catch (Exception e) { + throw new ShouldNeverHappenException(e); + } + } + + /** + * Object.toString() + * + * @param obj the obj + * @return string string + */ + public static String toString(Object obj) { + if (obj == null) { + return "null"; + } + if (obj.getClass().isPrimitive()) { + return String.valueOf(obj); + } + if (obj instanceof String) { + return (String)obj; + } + if (obj instanceof Number || obj instanceof Character || obj instanceof Boolean) { + return String.valueOf(obj); + } + if (obj instanceof Date) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S").format(obj); + } + if (obj instanceof Collection) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + if (!((Collection)obj).isEmpty()) { + for (Object o : (Collection)obj) { + sb.append(toString(o)).append(","); + } + sb.deleteCharAt(sb.length() - 1); + } + sb.append("]"); + return sb.toString(); + } + if (obj instanceof Map) { + Map map = (Map)obj; + StringBuilder sb = new StringBuilder(); + sb.append("{"); + if (!map.isEmpty()) { + map.forEach((key, value) -> { + sb.append(toString(key)).append("->") + .append(toString(value)).append(","); + }); + sb.deleteCharAt(sb.length() - 1); + } + sb.append("}"); + return sb.toString(); + } + StringBuilder sb = new StringBuilder(); + Field[] fields = obj.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + sb.append(field.getName()); + sb.append("="); + try { + Object f = field.get(obj); + if (f.getClass() == obj.getClass()) { + sb.append(f.toString()); + } else { + sb.append(toString(f)); + } + } catch (Exception e) { + } + sb.append(";"); + } + return sb.toString(); + } + + /** + * Trim string to null if empty(""). + * + * @param str the String to be trimmed, may be null + * @return the trimmed String + */ + public static String trimToNull(final String str) { + final String ts = trim(str); + return isEmpty(ts) ? null : ts; + } + + /** + * Trim string, or null if string is null. + * + * @param str the String to be trimmed, may be null + * @return the trimmed string, {@code null} if null String input + */ + public static String trim(final String str) { + return str == null ? null : str.trim(); + } + + /** + * Checks if a CharSequence is empty ("") or null. + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + * Checks if a CharSequence is not empty ("") and not null. + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is not empty and not null + */ + public static boolean isNotEmpty(final CharSequence cs) { + return !isEmpty(cs); + } +} diff --git a/common/src/test/java/io/seata/common/BranchDO.java b/common/src/test/java/io/seata/common/BranchDO.java new file mode 100644 index 0000000..b9935d2 --- /dev/null +++ b/common/src/test/java/io/seata/common/BranchDO.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.common; + +import java.util.Date; + +/** + * The branch do for test + * @author wangzhongxiang + */ +public class BranchDO { + private String xid; + private Long transactionId; + private Integer status; + private Double test; + private Date gmtCreate; + + public String getXid() { + return xid; + } + + public Long getTransactionId() { + return transactionId; + } + + public Integer getStatus() { + return status; + } + + public Double getTest() { + return test; + } + + public Date getGmtCreate() { + return gmtCreate; + } + + public BranchDO() { + } + + public BranchDO(String xid, Long transactionId, Integer status, Double test, + Date gmtCreate) { + this.xid = xid; + this.transactionId = transactionId; + this.status = status; + this.test = test; + this.gmtCreate = gmtCreate; + } +} diff --git a/common/src/test/java/io/seata/common/XIDTest.java b/common/src/test/java/io/seata/common/XIDTest.java new file mode 100644 index 0000000..9faa53b --- /dev/null +++ b/common/src/test/java/io/seata/common/XIDTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common; + +import java.util.Random; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Xid test. + * + * @author Otis.z + */ +public class XIDTest { + + /** + * Test set ip address. + */ + @Test + public void testSetIpAddress() { + XID.setIpAddress("127.0.0.1"); + assertThat(XID.getIpAddress()).isEqualTo("127.0.0.1"); + } + + /** + * Test set port. + */ + @Test + public void testSetPort() { + XID.setPort(8080); + assertThat(XID.getPort()).isEqualTo(8080); + } + + /** + * Test generate xid. + */ + @Test + public void testGenerateXID() { + long tranId = new Random().nextLong(); + XID.setPort(8080); + XID.setIpAddress("127.0.0.1"); + assertThat(XID.generateXID(tranId)).isEqualTo(XID.getIpAddress() + ":" + XID.getPort() + ":" + tranId); + } + + /** + * Test get transaction id. + */ + @Test + public void testGetTransactionId() { + assertThat(XID.getTransactionId(null)).isEqualTo(-1); + assertThat(XID.getTransactionId("127.0.0.1:8080:8577662204289747564")).isEqualTo(8577662204289747564L); + } +} diff --git a/common/src/test/java/io/seata/common/exception/DataAccessExceptionTest.java b/common/src/test/java/io/seata/common/exception/DataAccessExceptionTest.java new file mode 100644 index 0000000..92ef41f --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/DataAccessExceptionTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The dataAccess exception. + * + * @author lzf971107 + */ +public class DataAccessExceptionTest { + + @Test + public void testConstructorWithNoParameters() { + exceptionAsserts(new DataAccessException()); + } + + @Test + public void testConstructorWithFrameworkErrorCode() { + exceptionAsserts(new DataAccessException(FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithMessage() { + exceptionAsserts(new DataAccessException(FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + @Test + public void testConstructorWithMessageAndFrameworkErrorCode() { + exceptionAsserts(new DataAccessException(FrameworkErrorCode.UnknownAppError.getErrMessage(), FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithCauseExceptionMessageAndFrameworkErrorCode() { + exceptionAsserts(new DataAccessException(new Throwable(), FrameworkErrorCode.UnknownAppError.getErrMessage(), FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithThrowable() { + exceptionAsserts(new DataAccessException(new Throwable(FrameworkErrorCode.UnknownAppError.getErrMessage()))); + } + + private static void exceptionAsserts(DataAccessException exception) { + assertThat(exception).isInstanceOf(DataAccessException.class).hasMessage(FrameworkErrorCode.UnknownAppError.getErrMessage()); + assertThat(exception.getErrcode()).isEqualTo(FrameworkErrorCode.UnknownAppError); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/exception/EurekaRegistryExceptionTest.java b/common/src/test/java/io/seata/common/exception/EurekaRegistryExceptionTest.java new file mode 100644 index 0000000..ace71bb --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/EurekaRegistryExceptionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The eurekaRegistry exception. + * + * @author lzf971107 + */ +public class EurekaRegistryExceptionTest { + + @Test + public void testConstructorWithNoParameters() { + assertThat(new EurekaRegistryException()).isInstanceOf(EurekaRegistryException.class); + } + + @Test + public void testConstructorWithMessage() { + exceptionAsserts(new EurekaRegistryException(FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + @Test + public void testConstructorWithMessageAndThrowable() { + exceptionAsserts(new EurekaRegistryException(FrameworkErrorCode.UnknownAppError.getErrMessage(), new Throwable())); + } + + @Test + public void testConstructorWithThrowable() { + assertThat(new EurekaRegistryException(new Throwable(FrameworkErrorCode.UnknownAppError.getErrMessage()))).isInstanceOf(EurekaRegistryException.class).hasMessage("java.lang.Throwable: " + FrameworkErrorCode.UnknownAppError.getErrMessage()); + } + + private static void exceptionAsserts(EurekaRegistryException exception) { + assertThat(exception).isInstanceOf(EurekaRegistryException.class).hasMessage(FrameworkErrorCode.UnknownAppError.getErrMessage()); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/exception/FrameworkExceptionTest.java b/common/src/test/java/io/seata/common/exception/FrameworkExceptionTest.java new file mode 100644 index 0000000..cbb0142 --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/FrameworkExceptionTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import java.sql.SQLException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Framework exception test. + * + * @author Otis.z + */ +public class FrameworkExceptionTest { + + private Message message = new Message(); + + /** + * Test get errcode. + */ + @Test + public void testGetErrcode() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print4(); + }); + assertThat(throwable).hasMessage(FrameworkErrorCode.UnknownAppError.getErrMessage()); + assertThat(((FrameworkException)throwable).getErrcode()).isEqualTo(FrameworkErrorCode.UnknownAppError); + } + + /** + * Test nested exception. + */ + @Test + public void testNestedException() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print(); + }); + assertThat(throwable).hasMessage(""); + } + + /** + * Test nested exception 1. + */ + @Test + public void testNestedException1() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print1(); + }); + assertThat(throwable).hasMessage("nestedException"); + } + + /** + * Test nested exception 2. + */ + @Test + public void testNestedException2() { + Throwable throwable = Assertions.assertThrows(SQLException.class, () -> { + message.print2(); + }); + assertThat(throwable).hasMessageContaining("Message"); + } + + /** + * Test nested exception 3. + */ + @Test + public void testNestedException3() { + Throwable throwable = Assertions.assertThrows(SQLException.class, () -> { + message.print3(); + }); + assertThat(throwable).hasMessageContaining("Message"); + } + + /** + * Test nested exception 5. + */ + @Test + public void testNestedException5() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print5(); + }); + assertThat(throwable).hasMessage(FrameworkErrorCode.ExceptionCaught.getErrMessage()); + } + + /** + * Test nested exception 6. + */ + @Test + public void testNestedException6() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print6(); + }); + assertThat(throwable).hasMessage("frameworkException"); + } + + /** + * Test nested exception 7. + */ + @Test + public void testNestedException7() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print7(); + }); + assertThat(throwable).hasMessage("frameworkException"); + } + + /** + * Test nested exception 8. + */ + @Test + public void testNestedException8() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print8(); + }); + assertThat(throwable).hasMessage("throw"); + } + + /** + * Test nested exception 9. + */ + @Test + public void testNestedException9() { + Throwable throwable = Assertions.assertThrows(FrameworkException.class, () -> { + message.print9(); + }); + assertThat(throwable).hasMessage("frameworkExceptionMsg"); + } + + private static void exceptionAsserts(FrameworkException exception, String expectMessage) { + if (expectMessage == null) { + expectMessage = FrameworkErrorCode.UnknownAppError.getErrMessage(); + } + assertThat(exception).isInstanceOf(FrameworkException.class).hasMessage(expectMessage); + assertThat(exception.getErrcode()).isEqualTo(FrameworkErrorCode.UnknownAppError); + } + +} diff --git a/common/src/test/java/io/seata/common/exception/Message.java b/common/src/test/java/io/seata/common/exception/Message.java new file mode 100644 index 0000000..c41c9a8 --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/Message.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import java.sql.SQLException; + +/** + * The type Message. + * + * @author Otis.z + * @since 2019 /3/1 + */ +public class Message { + + /** + * Print. + */ + public void print() { + throw FrameworkException.nestedException(new Throwable(Message.class.getSimpleName())); + } + + /** + * Print 1. + */ + public void print1() { + throw FrameworkException.nestedException("nestedException", new Throwable(Message.class.getSimpleName())); + } + + /** + * Print 2. + * + * @throws SQLException the sql exception + */ + public void print2() throws SQLException { + throw FrameworkException.nestedSQLException("nestedException", new Throwable(Message.class.getSimpleName())); + } + + /** + * Print 3. + * + * @throws SQLException the sql exception + */ + public void print3() throws SQLException { + throw FrameworkException.nestedSQLException(new Throwable(Message.class.getSimpleName())); + } + + /** + * Print 4. + */ + public void print4() { + throw new FrameworkException(); + } + + /** + * Print 5. + */ + public void print5() { + throw new FrameworkException(FrameworkErrorCode.ExceptionCaught); + } + + /** + * Print 6. + */ + public void print6() { + throw new FrameworkException("frameworkException", FrameworkErrorCode.InitSeataClientError); + } + + /** + * Print 7. + */ + public void print7() { + throw new FrameworkException(new Throwable("throw"), "frameworkException", + FrameworkErrorCode.ChannelIsNotWritable); + } + + /** + * Print 8. + */ + public void print8() { + throw new FrameworkException(new Throwable("throw")); + } + + /** + * Print 9. + */ + public void print9() { + throw new FrameworkException(new Throwable(), "frameworkExceptionMsg"); + } + +} diff --git a/common/src/test/java/io/seata/common/exception/NotSupportYetExceptionTest.java b/common/src/test/java/io/seata/common/exception/NotSupportYetExceptionTest.java new file mode 100644 index 0000000..bcb8ec0 --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/NotSupportYetExceptionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The notSupportYet exception. + * + * @author lzf971107 + */ +public class NotSupportYetExceptionTest { + + @Test + public void testConstructorWithNoParameters() { + assertThat(new NotSupportYetException()).isInstanceOf(NotSupportYetException.class); + } + + @Test + public void testConstructorWithMessage() { + exceptionAsserts(new NotSupportYetException(FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + @Test + public void testConstructorWithMessageAndThrowable() { + exceptionAsserts(new NotSupportYetException(FrameworkErrorCode.UnknownAppError.getErrMessage(), new Throwable())); + } + + @Test + public void testConstructorWithThrowable() { + assertThat(new NotSupportYetException(new Throwable(FrameworkErrorCode.UnknownAppError.getErrMessage()))).isInstanceOf(NotSupportYetException.class).hasMessage("java.lang.Throwable: " + FrameworkErrorCode.UnknownAppError.getErrMessage()); + } + + private static void exceptionAsserts(NotSupportYetException exception) { + assertThat(exception).isInstanceOf(NotSupportYetException.class).hasMessage(FrameworkErrorCode.UnknownAppError.getErrMessage()); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/exception/ShouldNeverHappenExceptionTest.java b/common/src/test/java/io/seata/common/exception/ShouldNeverHappenExceptionTest.java new file mode 100644 index 0000000..34270ba --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/ShouldNeverHappenExceptionTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The shouldNeverHappen exception. + * + * @author lzf971107 + */ +public class ShouldNeverHappenExceptionTest { + + @Test + public void testConstructorWithNoParameters() { + assertThat(new ShouldNeverHappenException()).isInstanceOf(ShouldNeverHappenException.class); + } + + @Test + public void testConstructorWithMessage() { + exceptionAsserts(new ShouldNeverHappenException(FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + @Test + public void testConstructorWithMessageAndThrowable() { + exceptionAsserts(new ShouldNeverHappenException(FrameworkErrorCode.UnknownAppError.getErrMessage(), new Throwable())); + } + + @Test + public void testConstructorWithThrowable() { + assertThat(new ShouldNeverHappenException(new Throwable(FrameworkErrorCode.UnknownAppError.getErrMessage()))).isInstanceOf(ShouldNeverHappenException.class).hasMessage("java.lang.Throwable: " + FrameworkErrorCode.UnknownAppError.getErrMessage()); + } + + private static void exceptionAsserts(ShouldNeverHappenException exception) { + assertThat(exception).isInstanceOf(ShouldNeverHappenException.class).hasMessage(FrameworkErrorCode.UnknownAppError.getErrMessage()); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/exception/StoreExceptionTest.java b/common/src/test/java/io/seata/common/exception/StoreExceptionTest.java new file mode 100644 index 0000000..68aa46c --- /dev/null +++ b/common/src/test/java/io/seata/common/exception/StoreExceptionTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.exception; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StoreExceptionTest { + + @Test + public void testConstructorWithNoParameters() { + exceptionAsserts(new StoreException()); + } + + @Test + public void testConstructorWithFrameworkErrorCode() { + exceptionAsserts(new StoreException(FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithMessage() { + exceptionAsserts(new StoreException(FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + @Test + public void testConstructorWithMessageAndFrameworkErrorCode() { + exceptionAsserts( + new StoreException(FrameworkErrorCode.UnknownAppError.getErrMessage(), FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithCauseExceptionMessageAndFrameworkErrorCode() { + exceptionAsserts(new StoreException(new Throwable(), FrameworkErrorCode.UnknownAppError.getErrMessage(), + FrameworkErrorCode.UnknownAppError)); + } + + @Test + public void testConstructorWithThrowable() { + exceptionAsserts(new StoreException(new Throwable(FrameworkErrorCode.UnknownAppError.getErrMessage()))); + } + + @Test + public void testConstructorWithThrowableAndMessage() { + exceptionAsserts(new StoreException(new Throwable(), FrameworkErrorCode.UnknownAppError.getErrMessage())); + } + + private static void exceptionAsserts(StoreException exception) { + assertThat(exception).isInstanceOf(StoreException.class).hasMessage( + FrameworkErrorCode.UnknownAppError.getErrMessage()); + assertThat(exception.getErrcode()).isEqualTo(FrameworkErrorCode.UnknownAppError); + } +} diff --git a/common/src/test/java/io/seata/common/loader/ChineseHello.java b/common/src/test/java/io/seata/common/loader/ChineseHello.java new file mode 100644 index 0000000..f2949a1 --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/ChineseHello.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * The type Chinese hello. + * + * @author Otis.z + */ +@LoadLevel(name = "ChineseHello", order = Integer.MIN_VALUE) +public class ChineseHello implements Hello { + + @Override + public String say() { + return "ni hao!"; + } +} diff --git a/common/src/test/java/io/seata/common/loader/EnglishHello.java b/common/src/test/java/io/seata/common/loader/EnglishHello.java new file mode 100644 index 0000000..028b2da --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/EnglishHello.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * The type English hello. + * + * @author Otis.z + */ +@LoadLevel(name = "EnglishHello", order = 1) +public class EnglishHello implements Hello { + + @Override + public String say() { + return "hello!"; + } +} diff --git a/common/src/test/java/io/seata/common/loader/EnhancedServiceLoaderTest.java b/common/src/test/java/io/seata/common/loader/EnhancedServiceLoaderTest.java new file mode 100644 index 0000000..ae9f262 --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/EnhancedServiceLoaderTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Enhanced service loader test. + * + * @author Otis.z + */ +public class EnhancedServiceLoaderTest { + + /** + * Test load by class and class loader. + */ + @Test + public void testLoadByClassAndClassLoader() { + Hello load = EnhancedServiceLoader.load(Hello.class, Hello.class.getClassLoader()); + Assertions.assertEquals(load.say(), "Olá."); + } + + /** + * Test load exception. + */ + @Test + public void testLoadException() { + Assertions.assertThrows(EnhancedServiceNotFoundException.class, () -> { + EnhancedServiceLoaderTest load = EnhancedServiceLoader.load(EnhancedServiceLoaderTest.class); + }); + } + + /** + * Test load by class. + */ + @Test + public void testLoadByClass() { + Hello load = EnhancedServiceLoader.load(Hello.class); + assertThat(load.say()).isEqualTo("Olá."); + } + + /** + * Test load by class and activate name. + */ + @Test + public void testLoadByClassAndActivateName() { + Hello englishHello = EnhancedServiceLoader.load(Hello.class, "EnglishHello"); + assertThat(englishHello.say()).isEqualTo("hello!"); + } + + /** + * Test load by class and class loader and activate name. + */ + @Test + public void testLoadByClassAndClassLoaderAndActivateName() { + Hello englishHello = EnhancedServiceLoader + .load(Hello.class, "EnglishHello", EnhancedServiceLoaderTest.class.getClassLoader()); + assertThat(englishHello.say()).isEqualTo("hello!"); + } + + /** + * Gets all extension class. + */ + @Test + public void getAllExtensionClass() { + List allExtensionClass = EnhancedServiceLoader.getAllExtensionClass(Hello.class); + assertThat(allExtensionClass.get(3).getSimpleName()).isEqualTo((LatinHello.class.getSimpleName())); + assertThat(allExtensionClass.get(2).getSimpleName()).isEqualTo((FrenchHello.class.getSimpleName())); + assertThat(allExtensionClass.get(1).getSimpleName()).isEqualTo((EnglishHello.class.getSimpleName())); + assertThat(allExtensionClass.get(0).getSimpleName()).isEqualTo((ChineseHello.class.getSimpleName())); + } + + /** + * Gets all extension class 1. + */ + @Test + public void getAllExtensionClass1() { + List allExtensionClass = EnhancedServiceLoader + .getAllExtensionClass(Hello.class, ClassLoader.getSystemClassLoader()); + assertThat(allExtensionClass).isNotEmpty(); + } + + @Test + public void getSingletonExtensionInstance(){ + Hello hello1 = EnhancedServiceLoader.load(Hello.class, "ChineseHello"); + Hello hello2 = EnhancedServiceLoader.load(Hello.class, "ChineseHello"); + assertThat(hello1 == hello2).isTrue(); + } + + @Test + public void getMultipleExtensionInstance(){ + Hello hello1 = EnhancedServiceLoader.load(Hello.class, "LatinHello"); + Hello hello2 = EnhancedServiceLoader.load(Hello.class, "LatinHello"); + assertThat(hello1 == hello2).isFalse(); + } + + @Test + public void getAllInstances(){ + List hellows1 = EnhancedServiceLoader.loadAll(Hello.class); + List hellows2 = EnhancedServiceLoader.loadAll(Hello.class); + for (Hello hello : hellows1){ + if (!hello.say().equals("Olá.")) { + assertThat(hellows2.contains(hello)).isTrue(); + } + else{ + assertThat(hellows2.contains(hello)).isFalse(); + } + } + } + +} diff --git a/common/src/test/java/io/seata/common/loader/FrenchHello.java b/common/src/test/java/io/seata/common/loader/FrenchHello.java new file mode 100644 index 0000000..a7c3832 --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/FrenchHello.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * The type French hello. + * + * @author Otis.z + */ +@LoadLevel(name = "FrenchHello", order = 2) +public class FrenchHello implements Hello { + + @Override + public String say() { + return "Bonjour"; + } +} diff --git a/common/src/test/java/io/seata/common/loader/Hello.java b/common/src/test/java/io/seata/common/loader/Hello.java new file mode 100644 index 0000000..cb1bb9e --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/Hello.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * The interface Hello. + * + * @author Otis.z + */ +public interface Hello { + /** + * Say string. + * + * @return the string + */ + String say(); +} diff --git a/common/src/test/java/io/seata/common/loader/LatinHello.java b/common/src/test/java/io/seata/common/loader/LatinHello.java new file mode 100644 index 0000000..458b10c --- /dev/null +++ b/common/src/test/java/io/seata/common/loader/LatinHello.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.loader; + +/** + * The type LatinHello + * + * @author haozhibei + */ +@LoadLevel(name = "LatinHello",order = 3, scope = Scope.PROTOTYPE) +public class LatinHello implements Hello { + @Override + public String say() { + return "Olá."; + } +} diff --git a/common/src/test/java/io/seata/common/rpc/RpcStatusTest.java b/common/src/test/java/io/seata/common/rpc/RpcStatusTest.java new file mode 100644 index 0000000..8dac944 --- /dev/null +++ b/common/src/test/java/io/seata/common/rpc/RpcStatusTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.common.rpc; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The state statistics test. + * + * @author ph3636 + */ +public class RpcStatusTest { + + public static final String SERVICE = "127.0.0.1:80"; + + @Test + public void getStatus() { + RpcStatus rpcStatus1 = RpcStatus.getStatus(SERVICE); + Assertions.assertNotNull(rpcStatus1); + RpcStatus rpcStatus2 = RpcStatus.getStatus(SERVICE); + Assertions.assertEquals(rpcStatus1, rpcStatus2); + } + + @Test + public void removeStatus() { + RpcStatus old = RpcStatus.getStatus(SERVICE); + RpcStatus.removeStatus(SERVICE); + Assertions.assertNotEquals(RpcStatus.getStatus(SERVICE), old); + } + + @Test + public void beginCount() { + RpcStatus.beginCount(SERVICE); + Assertions.assertEquals(RpcStatus.getStatus(SERVICE).getActive(), 1); + } + + @Test + public void endCount() { + RpcStatus.endCount(SERVICE); + Assertions.assertEquals(RpcStatus.getStatus(SERVICE).getActive(), 0); + Assertions.assertEquals(RpcStatus.getStatus(SERVICE).getTotal(), 1); + } +} diff --git a/common/src/test/java/io/seata/common/thread/NamedThreadFactoryTest.java b/common/src/test/java/io/seata/common/thread/NamedThreadFactoryTest.java new file mode 100644 index 0000000..84603c4 --- /dev/null +++ b/common/src/test/java/io/seata/common/thread/NamedThreadFactoryTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +/** + * @author Otis.z + */ +public class NamedThreadFactoryTest { + private static final int THREAD_TOTAL_SIZE = 3; + private static final int DEFAULT_THREAD_PREFIX_COUNTER = 1; + + @Test + public void testNewThread() { + NamedThreadFactory namedThreadFactory = new NamedThreadFactory("testNameThread", 5); + + Thread testNameThread = namedThreadFactory + .newThread(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + assertThat(testNameThread.getName()).startsWith("testNameThread"); + assertThat(testNameThread.isDaemon()).isTrue(); + } + + @Test + public void testConstructorWithPrefixAndDaemons() { + NamedThreadFactory factory = new NamedThreadFactory("prefix", true); + Thread thread = factory.newThread(() -> {}); + + assertThat(thread.getName()).startsWith("prefix"); + assertThat(thread.isDaemon()).isTrue(); + } + + + @Test + public void testThreadNameHasCounterWithPrefixCounter() { + NamedThreadFactory factory = new NamedThreadFactory("prefix", THREAD_TOTAL_SIZE, true); + for (int i = 0; i < THREAD_TOTAL_SIZE; i ++) { + Thread thread = factory.newThread(() -> {}); + + + // the first _DEFAULT_THREAD_PREFIX_COUNTER is meaning thread counter + assertThat("prefix_" + DEFAULT_THREAD_PREFIX_COUNTER + "_" + (i + 1) + "_" + THREAD_TOTAL_SIZE) + .isEqualTo(thread.getName()); + } + } + + @Test + public void testNamedThreadFactoryWithSecurityManager() { + NamedThreadFactory factory = new NamedThreadFactory("testThreadGroup", true); + Thread thread = factory.newThread(() -> {}); + assertThat(thread.getThreadGroup()).isNotNull(); + } + +} diff --git a/common/src/test/java/io/seata/common/thread/PositiveAtomicCounterTest.java b/common/src/test/java/io/seata/common/thread/PositiveAtomicCounterTest.java new file mode 100644 index 0000000..6c5a456 --- /dev/null +++ b/common/src/test/java/io/seata/common/thread/PositiveAtomicCounterTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PositiveAtomicCounterTest { + + @Test + public void testConstructor() { + PositiveAtomicCounter counter = new PositiveAtomicCounter(); + assertThat(counter).isInstanceOf(PositiveAtomicCounter.class); + } + + @Test + public void testIncrementAndGet() { + PositiveAtomicCounter counter = new PositiveAtomicCounter(); + assertThat(counter.incrementAndGet()).isEqualTo(1); + } + + @Test + public void testGetAndIncrement() { + PositiveAtomicCounter counter = new PositiveAtomicCounter(); + assertThat(counter.getAndIncrement()).isEqualTo(0); + } + + @Test + public void testGet() { + PositiveAtomicCounter counter = new PositiveAtomicCounter(); + assertThat(counter.get()).isEqualTo(0); + } +} diff --git a/common/src/test/java/io/seata/common/thread/RejectedPoliciesTest.java b/common/src/test/java/io/seata/common/thread/RejectedPoliciesTest.java new file mode 100644 index 0000000..1fb2c70 --- /dev/null +++ b/common/src/test/java/io/seata/common/thread/RejectedPoliciesTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.thread; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Created by guoyao on 2019/2/26. + */ +public class RejectedPoliciesTest { + + private final int DEFAULT_CORE_POOL_SIZE = 1; + private final int DEFAULT_KEEP_ALIVE_TIME = 10; + private final int MAX_QUEUE_SIZE = 1; + + /** + * Test runs oldest task policy. + * + * @throws Exception the exception + */ + @Test + public void testRunsOldestTaskPolicy() throws Exception { + AtomicInteger atomicInteger = new AtomicInteger(); + ThreadPoolExecutor poolExecutor = + new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_CORE_POOL_SIZE, DEFAULT_KEEP_ALIVE_TIME, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), + new NamedThreadFactory("OldestRunsPolicy", DEFAULT_CORE_POOL_SIZE), + RejectedPolicies.runsOldestTaskPolicy()); + CountDownLatch downLatch1 = new CountDownLatch(1); + CountDownLatch downLatch2 = new CountDownLatch(1); + CountDownLatch downLatch3 = new CountDownLatch(1); + //task1 + poolExecutor.execute(() -> { + try { + //wait the oldest task of queue count down + downLatch1.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + atomicInteger.getAndAdd(1); + }); + assertThat(atomicInteger.get()).isEqualTo(0); + //task2 + poolExecutor.execute(() -> { + // run second + atomicInteger.getAndAdd(2); + }); + //task3 + poolExecutor.execute(() -> { + downLatch2.countDown(); + //task3 run + atomicInteger.getAndAdd(3); + downLatch3.countDown(); + }); + //only the task2 run which is the oldest task of queue + assertThat(atomicInteger.get()).isEqualTo(2); + downLatch1.countDown(); + downLatch2.await(); + //wait task3 run +3 + downLatch3.await(); + //run task3 + assertThat(atomicInteger.get()).isEqualTo(6); + + } +} diff --git a/common/src/test/java/io/seata/common/util/BeanUtilsTest.java b/common/src/test/java/io/seata/common/util/BeanUtilsTest.java new file mode 100644 index 0000000..04fda80 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/BeanUtilsTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import io.seata.common.BranchDO; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The bean utils test + * + * @author wangzhongxiang + */ +public class BeanUtilsTest { + + @Test + public void testBeanToStringNotNull() { + BranchDO branchDO = new BranchDO("xid123123", 123L, 1, 2.2, new Date()); + Assertions.assertNotNull(BeanUtils.beanToString(branchDO)); + } + @Test + public void testBeanToStringNull() { + BranchDO branchDO = null; + Assertions.assertNull(BeanUtils.beanToString(branchDO)); + } + + @Test + public void testMapToObjectNotNull() { + Map map = new HashMap<>(); + Date date = new Date(); + map.put("xid", "192.166.166.11:9010:12423424234234"); + map.put("transactionId", "12423424234234"); + map.put("status", "2"); + map.put("test", "22.22"); + map.put("gmtCreate", String.valueOf(date.getTime())); + BranchDO branchDO = + (BranchDO) BeanUtils.mapToObject(map, BranchDO.class); + Assertions.assertEquals(map.get("xid"), branchDO.getXid()); + Assertions.assertEquals(Long.valueOf(map.get("transactionId")), branchDO.getTransactionId()); + Assertions.assertEquals(Integer.valueOf(map.get("status")), branchDO.getStatus()); + Assertions.assertEquals(Double.valueOf(map.get("test")), branchDO.getTest()); + Assertions.assertEquals(new Date(date.getTime()), branchDO.getGmtCreate()); + } + + @Test + public void testMapToObjectNull() { + Map map = null; + BranchDO branchDO = + (BranchDO) BeanUtils.mapToObject(map, BranchDO.class); + Assertions.assertNull(branchDO); + } + + @Test + public void testObjectToMapNotNull() { + BranchDO branchDO = new BranchDO("xid123123", 123L, 1, 2.2, new Date()); + Map map = BeanUtils.objectToMap(branchDO); + Assertions.assertEquals(branchDO.getXid(), map.get("xid")); + Assertions.assertEquals(branchDO.getTransactionId(), Long.valueOf(map.get("transactionId"))); + Assertions.assertEquals(branchDO.getStatus(), Integer.valueOf(map.get("status"))); + Assertions.assertEquals(branchDO.getTest(), Double.valueOf(map.get("test"))); + Assertions.assertEquals(branchDO.getGmtCreate().getTime(),Long.valueOf(map.get("gmtCreate"))); + } + + @Test + public void testObjectToMapNull() { + BranchDO branchDO = null; + Map map = BeanUtils.objectToMap(branchDO); + Assertions.assertNull(map); + } + +} diff --git a/common/src/test/java/io/seata/common/util/BlobUtilsTest.java b/common/src/test/java/io/seata/common/util/BlobUtilsTest.java new file mode 100644 index 0000000..51eafc4 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/BlobUtilsTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.io.UnsupportedEncodingException; +import java.sql.SQLException; + +import javax.sql.rowset.serial.SerialBlob; + +import io.seata.common.Constants; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * The type Blob utils test. + * + * @author Otis.z + * @author Geng Zhang + */ +public class BlobUtilsTest { + + /** + * Test string 2 blob. + * + * @throws SQLException the sql exception + */ + @Test + public void testString2blob() throws SQLException { + assertNull(BlobUtils.string2blob(null)); + assertThat(BlobUtils.string2blob("123abc")).isEqualTo( + new SerialBlob("123abc".getBytes(Constants.DEFAULT_CHARSET))); + } + + /** + * Test blob 2 string. + * + * @throws SQLException the sql exception + */ + @Test + public void testBlob2string() throws SQLException { + assertNull(BlobUtils.blob2string(null)); + assertThat(BlobUtils.blob2string(new SerialBlob("123absent".getBytes(Constants.DEFAULT_CHARSET)))).isEqualTo( + "123absent"); + } + + @Test + void bytes2Blob() throws UnsupportedEncodingException, SQLException { + assertNull(BlobUtils.bytes2Blob(null)); + byte[] bs = "xxaaadd".getBytes(Constants.DEFAULT_CHARSET_NAME); + assertThat(BlobUtils.bytes2Blob(bs)).isEqualTo( + new SerialBlob(bs)); + } + + @Test + void blob2Bytes() throws UnsupportedEncodingException, SQLException { + assertNull(BlobUtils.blob2Bytes(null)); + byte[] bs = "xxaaadd".getBytes(Constants.DEFAULT_CHARSET_NAME); + assertThat(BlobUtils.blob2Bytes(new SerialBlob(bs))).isEqualTo( + bs); + } +} diff --git a/common/src/test/java/io/seata/common/util/CollectionUtilsTest.java b/common/src/test/java/io/seata/common/util/CollectionUtilsTest.java new file mode 100644 index 0000000..f91e17d --- /dev/null +++ b/common/src/test/java/io/seata/common/util/CollectionUtilsTest.java @@ -0,0 +1,239 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Collection utils test. + * + * @author Geng Zhang + */ +public class CollectionUtilsTest { + + @Test + public void test_isEmpty_isNotEmpty() { + // case 1: null + List list = null; + String[] array = null; + Map map = null; + Assertions.assertTrue(CollectionUtils.isEmpty(list)); + Assertions.assertTrue(CollectionUtils.isEmpty(array)); + Assertions.assertTrue(CollectionUtils.isEmpty(map)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(list)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(array)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(map)); + + // case 2: empty + list = new ArrayList<>(); + array = new String[0]; + map = new HashMap<>(); + Assertions.assertTrue(CollectionUtils.isEmpty(list)); + Assertions.assertTrue(CollectionUtils.isEmpty(array)); + Assertions.assertTrue(CollectionUtils.isEmpty(map)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(list)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(array)); + Assertions.assertFalse(CollectionUtils.isNotEmpty(map)); + + // case 3: not empty + list.add("1"); + array = new String[]{"1"}; + map.put("test", "test"); + Assertions.assertFalse(CollectionUtils.isEmpty(list)); + Assertions.assertFalse(CollectionUtils.isEmpty(array)); + Assertions.assertFalse(CollectionUtils.isEmpty(map)); + Assertions.assertTrue(CollectionUtils.isNotEmpty(list)); + Assertions.assertTrue(CollectionUtils.isNotEmpty(array)); + Assertions.assertTrue(CollectionUtils.isNotEmpty(map)); + } + + /** + * Is size equals. + */ + @Test + public void isSizeEquals() { + List list0 = new ArrayList<>(); + List list1 = new ArrayList<>(); + Assertions.assertTrue(CollectionUtils.isSizeEquals(null, null)); + Assertions.assertFalse(CollectionUtils.isSizeEquals(null, list0)); + Assertions.assertFalse(CollectionUtils.isSizeEquals(list1, null)); + Assertions.assertTrue(CollectionUtils.isSizeEquals(list0, list1)); + + list0.add("111"); + Assertions.assertFalse(CollectionUtils.isSizeEquals(list0, list1)); + list1.add("111"); + Assertions.assertTrue(CollectionUtils.isSizeEquals(list0, list1)); + } + + /** + * Encode map. + */ + @Test + public void encodeMap() { + Map map = null; + Assertions.assertNull(CollectionUtils.encodeMap(map)); + + map = new LinkedHashMap<>(); + Assertions.assertEquals("", CollectionUtils.encodeMap(map)); + map.put("x", "1"); + Assertions.assertEquals("x=1", CollectionUtils.encodeMap(map)); + map.put("y", "2"); + Assertions.assertEquals("x=1&y=2", CollectionUtils.encodeMap(map)); + } + + /** + * Decode map. + */ + @Test + public void decodeMap() { + Assertions.assertNull(CollectionUtils.decodeMap(null)); + + Map map = CollectionUtils.decodeMap(""); + Assertions.assertEquals(0, map.size()); + + map = CollectionUtils.decodeMap("&"); + Assertions.assertEquals(0, map.size()); + + map = CollectionUtils.decodeMap("="); + Assertions.assertEquals(0, map.size()); + + map = CollectionUtils.decodeMap("&="); + Assertions.assertEquals(0, map.size()); + + map = CollectionUtils.decodeMap("x=1"); + Assertions.assertEquals(1, map.size()); + Assertions.assertEquals("1", map.get("x")); + + map = CollectionUtils.decodeMap("x=1&y=2"); + Assertions.assertEquals(2, map.size()); + Assertions.assertEquals("2", map.get("y")); + } + + /** + * Test to upper list. + */ + @Test + public void testToUpperList() { + List sourceList = null; + Assertions.assertNull(CollectionUtils.toUpperList(sourceList)); + sourceList = new ArrayList<>(); + Assertions.assertEquals(Collections.EMPTY_LIST, CollectionUtils.toUpperList(sourceList)); + List anotherList = new ArrayList<>(); + sourceList.add("a"); + anotherList.add("A"); + sourceList.add("b"); + anotherList.add("b"); + sourceList.add("c"); + anotherList.add("C"); + Assertions.assertEquals(CollectionUtils.toUpperList(sourceList), CollectionUtils.toUpperList(anotherList)); + anotherList.add("D"); + Assertions.assertTrue( + CollectionUtils.toUpperList(anotherList).containsAll(CollectionUtils.toUpperList(sourceList))); + + List listWithNull = new ArrayList<>(); + listWithNull.add("foo"); + listWithNull.add(null); + listWithNull.add("bar"); + + List listUpperWithNull = new ArrayList<>(); + listUpperWithNull.add("FOO"); + listUpperWithNull.add(null); + listUpperWithNull.add("BAR"); + Assertions.assertEquals(listUpperWithNull, CollectionUtils.toUpperList(listWithNull)); + } + + @Test + public void testIsEmptyWithArrays() { + String[] emptyArray = {}; + String[] filledArray = {"Foo", "Bar"}; + + Assertions.assertTrue(CollectionUtils.isEmpty(emptyArray)); + Assertions.assertFalse(CollectionUtils.isEmpty(filledArray)); + } + + @Test + public void testIsEmptyWithCollection() { + List emptyCollection = new ArrayList<>(); + List filledCollection = new ArrayList<>(); + + filledCollection.add("Foo"); + filledCollection.add("Bar"); + + Assertions.assertTrue(CollectionUtils.isEmpty(emptyCollection)); + Assertions.assertFalse(CollectionUtils.isEmpty(filledCollection)); + } + + @Test + public void testCollectionToString() { + List emptyCollection = new ArrayList<>(); + List filledCollection = new ArrayList<>(); + + filledCollection.add("Foo"); + filledCollection.add("Bar"); + + Assertions.assertEquals("", CollectionUtils.toString(emptyCollection)); + Assertions.assertEquals("[Foo,Bar]", CollectionUtils.toString(filledCollection)); + } + + @Test + public void testIsEmpty() { + Map map = new HashMap<>(); + Assertions.assertTrue(CollectionUtils.isEmpty(map)); + map.put("k", "v"); + Assertions.assertFalse(CollectionUtils.isEmpty(map)); + map = null; + Assertions.assertTrue(CollectionUtils.isEmpty(map)); + } + + @Test + public void testObjectMapToStringMap() { + Map objMap = new HashMap<>(); + Date now = new Date(); + objMap.put("a", "aa"); + objMap.put("b", 22); + objMap.put("c", now); + Map strMap = CollectionUtils.toStringMap(objMap); + Assertions.assertEquals("aa", strMap.get("a")); + Assertions.assertEquals("22", strMap.get("b")); + Assertions.assertEquals(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S").format(now), strMap.get("c")); + } + + @Test + public void test_getLast() { + // case 1: null + Assertions.assertNull(CollectionUtils.getLast(null)); + + // case 2: empty + List emptyList = Collections.EMPTY_LIST; + Assertions.assertNull(CollectionUtils.getLast(emptyList)); + + // case 3: not empty + List list = new ArrayList<>(); + list.add("Foo"); + list.add("Bar"); + Assertions.assertEquals("Bar", CollectionUtils.getLast(list)); + } +} diff --git a/common/src/test/java/io/seata/common/util/CompressUtilTest.java b/common/src/test/java/io/seata/common/util/CompressUtilTest.java new file mode 100644 index 0000000..45bcee2 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/CompressUtilTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.io.IOException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CompressUtilTest { + + @Test + public void testCompress() throws IOException { + byte[] bytes = new byte[]{31, -117, 8, 0, 0, 0, 0, 0, 0, 0, + 99, 100, 98, 6, 0, 29, -128, -68, 85, 3, 0, 0, 0}; + + Assertions.assertArrayEquals(bytes, + CompressUtil.compress(new byte[]{1, 2, 3})); + } + + @Test + public void testUncompress() throws IOException { + byte[] bytes = new byte[]{31, -117, 8, 0, 0, 0, 0, 0, 0, 0, + 99, 100, 98, 6, 0, 29, -128, -68, 85, 3, 0, 0, 0}; + + Assertions.assertArrayEquals(new byte[]{1, 2, 3}, + CompressUtil.uncompress(bytes)); + } + + @Test + public void testIsCompressData() { + Assertions.assertFalse(CompressUtil.isCompressData(null)); + Assertions.assertFalse(CompressUtil.isCompressData(new byte[0])); + Assertions.assertFalse(CompressUtil.isCompressData(new byte[]{31, 11})); + Assertions.assertFalse( + CompressUtil.isCompressData(new byte[]{31, 11, 0})); + + Assertions.assertTrue( + CompressUtil.isCompressData(new byte[]{31, -117, 0})); + } +} diff --git a/common/src/test/java/io/seata/common/util/ConfigToolsTest.java b/common/src/test/java/io/seata/common/util/ConfigToolsTest.java new file mode 100644 index 0000000..bbda4e0 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/ConfigToolsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.security.KeyPair; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author funkye + */ +public class ConfigToolsTest { + + @Test + public void test() throws Exception { + KeyPair keyPair = ConfigTools.getKeyPair(); + String publicKeyStr = ConfigTools.getPublicKey(keyPair); + String privateKeyStr = ConfigTools.getPrivateKey(keyPair); + System.out.println("publicKeyStr:" + publicKeyStr); + System.out.println("privateKeyStr:" + privateKeyStr); + String password = "123456"; + String byte2Base64 = ConfigTools.privateEncrypt(password, privateKeyStr); + System.out.println("byte2Base64:" + byte2Base64); + String pw = ConfigTools.publicDecrypt(byte2Base64, publicKeyStr); + Assertions.assertEquals(pw, password); + } + +} diff --git a/common/src/test/java/io/seata/common/util/DurationUtilTest.java b/common/src/test/java/io/seata/common/util/DurationUtilTest.java new file mode 100644 index 0000000..1736a34 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/DurationUtilTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DurationUtilTest { + + @Test + public void testParse() { + Assertions.assertNull(DurationUtil.parse("d")); + Assertions.assertNull(DurationUtil.parse("h")); + Assertions.assertNull(DurationUtil.parse("m")); + Assertions.assertNull(DurationUtil.parse("s")); + Assertions.assertNull(DurationUtil.parse("ms")); + + Assertions.assertEquals(-1L, DurationUtil.parse("").getSeconds()); + Assertions.assertEquals(0L, DurationUtil.parse("8").getSeconds()); + Assertions.assertEquals(0L, DurationUtil.parse("8ms").getSeconds()); + Assertions.assertEquals(8L, DurationUtil.parse("8s").getSeconds()); + Assertions.assertEquals(480L, DurationUtil.parse("8m").getSeconds()); + Assertions.assertEquals(28800L, DurationUtil.parse("8h").getSeconds()); + Assertions.assertEquals(691200L, + DurationUtil.parse("8d").getSeconds()); + } + + @Test + public void testParseThrowException() { + Assertions.assertThrows(UnsupportedOperationException.class, + () -> DurationUtil.parse("a")); + + Assertions.assertThrows(UnsupportedOperationException.class, + () -> DurationUtil.parse("as")); + + } +} diff --git a/common/src/test/java/io/seata/common/util/IOUtilTest.java b/common/src/test/java/io/seata/common/util/IOUtilTest.java new file mode 100644 index 0000000..7255a36 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/IOUtilTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author caioguedes + */ +public class IOUtilTest { + + @Test + public void testCloseWithSingleParameter() { + FakeResource resource = new FakeResource(); + + IOUtil.close(resource); + + Assertions.assertTrue(resource.isClose()); + } + + @Test + public void testCloseWithArrayParameter() { + FakeResource resource1 = new FakeResource(); + FakeResource resource2 = new FakeResource(); + + IOUtil.close(resource1, resource2); + + Assertions.assertTrue(resource1.isClose()); + Assertions.assertTrue(resource2.isClose()); + } + + @Test + public void testIgnoreExceptionOnClose() { + FakeResource resource = new FakeResource() { + @Override + public void close() throws Exception { + super.close(); + throw new Exception("Ops!"); + } + }; + + IOUtil.close(resource); + + Assertions.assertTrue(resource.isClose()); + } + + private class FakeResource implements AutoCloseable{ + private boolean close = false; + + @Override + public void close() throws Exception { + this.close = true; + } + + public boolean isClose() { + return close; + } + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/util/IdWorkerTest.java b/common/src/test/java/io/seata/common/util/IdWorkerTest.java new file mode 100644 index 0000000..196c092 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/IdWorkerTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class IdWorkerTest { + + @Test + void testNegativeWorkerId() { + assertThrows(IllegalArgumentException.class, () -> { + new IdWorker(-1L); + }, "should throw IllegalArgumentException when workerId is negative"); + } + + @Test + void testTooLargeWorkerId() { + assertThrows(IllegalArgumentException.class, () -> { + new IdWorker(1024L); + }, "should throw IllegalArgumentException when workerId is bigger than 1023"); + } + + @Test + void testNextId() { + IdWorker worker = new IdWorker(null); + long id1 = worker.nextId(); + long id2 = worker.nextId(); + assertEquals(1L, id2 - id1, "increment step should be 1"); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/util/NetUtilTest.java b/common/src/test/java/io/seata/common/util/NetUtilTest.java new file mode 100644 index 0000000..c830e0f --- /dev/null +++ b/common/src/test/java/io/seata/common/util/NetUtilTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * The type Net util test. + * + * @author Otis.z + */ +public class NetUtilTest { + + private InetSocketAddress ipv4 = new InetSocketAddress(Inet4Address.getLocalHost().getHostName(), 3902); + private InetSocketAddress ipv6 = new InetSocketAddress(Inet6Address.getLocalHost().getHostName(), 3904); + + /** + * Instantiates a new Net util test. + * + * @throws UnknownHostException the unknown host exception + */ + public NetUtilTest() throws UnknownHostException { + } + + /** + * Test to string address. + */ + @Test + public void testToStringAddress() { + try { + String stringAddress = NetUtil.toStringAddress(InetSocketAddress.createUnresolved("127.0.0.1", 9828)); + } catch (Exception e) { + assertThat(e).isInstanceOf(NullPointerException.class); + } + } + + /** + * Test to string address 1. + */ + @Test + public void testToStringAddress1() { + assertThat(NetUtil.toStringAddress((SocketAddress)ipv4)) + .isEqualTo(ipv4.getAddress().getHostAddress() + ":" + ipv4.getPort()); + assertThat(NetUtil.toStringAddress((SocketAddress)ipv6)).isEqualTo( + ipv6.getAddress().getHostAddress() + ":" + ipv6.getPort()); + } + + /** + * Test to string address 2. + */ + @Test + public void testToStringAddress2() { + assertThat(NetUtil.toStringAddress(ipv4)).isEqualTo( + ipv4.getAddress().getHostAddress() + ":" + ipv4.getPort()); + assertThat(NetUtil.toStringAddress(ipv6)).isEqualTo( + ipv6.getAddress().getHostAddress() + ":" + ipv6.getPort()); + } + + /** + * Test to ip address. + * + * @throws UnknownHostException the unknown host exception + */ + @Test + public void testToIpAddress() throws UnknownHostException { + assertThat(NetUtil.toIpAddress(ipv4)).isEqualTo(ipv4.getAddress().getHostAddress()); + assertThat(NetUtil.toIpAddress(ipv6)).isEqualTo(ipv6.getAddress().getHostAddress()); + } + + /** + * Test to inet socket address. + */ + @Test + public void testToInetSocketAddress() { + try { + NetUtil.toInetSocketAddress("23939:ks"); + } catch (Exception e) { + assertThat(e).isInstanceOf(NumberFormatException.class); + } + } + + /** + * Test to inet socket address 1. + */ + @Test + public void testToInetSocketAddress1() { + assertThat(NetUtil.toInetSocketAddress("kadfskl").getHostName()).isEqualTo("kadfskl"); + } + + /** + * Test to long. + */ + @Test + public void testToLong() { + try { + NetUtil.toLong("kdskdsfk"); + } catch (Exception e) { + assertThat(e).isInstanceOf(NullPointerException.class); + } + } + + /** + * Test to long 1. + */ + @Test + public void testToLong1() { + String[] split = "127.0.0.1".split("\\."); + long r = 0; + r = r | (Long.parseLong(split[0]) << 40); + r = r | (Long.parseLong(split[1]) << 32); + r = r | (Long.parseLong(split[2]) << 24); + r = r | (Long.parseLong(split[3]) << 16); + assertThat(NetUtil.toLong("127.0.0.1")).isEqualTo(r); + + } + + /** + * Test get local ip. + */ + @Test + public void testGetLocalIp() { + assertThat(NetUtil.getLocalIp()).isNotNull(); + } + + /** + * Test get local host. + */ + @Test + public void testGetLocalHost() { + assertThat(NetUtil.getLocalHost()).isNotNull(); + } + + /** + * Test get local address. + */ + @Test + public void testGetLocalAddress() { + assertThat(NetUtil.getLocalAddress()).isNotNull(); + } + + @Test + public void testIsValidIp() { + String localIp = "127.0.0.1"; + String someIp = "8.210.212.91"; + String someHostName = "seata.io"; + String unknownHost = "knownHost"; + assertThat(NetUtil.isValidIp(localIp, true)).isTrue(); + assertThat(NetUtil.isValidIp(localIp, false)).isFalse(); + + assertThat(NetUtil.isValidIp(someIp, true)).isTrue(); + assertThat(NetUtil.isValidIp(someIp, false)).isTrue(); + + assertThat(NetUtil.isValidIp(someHostName, true)).isTrue(); + assertThat(NetUtil.isValidIp(someHostName, false)).isTrue(); + + assertThatThrownBy(() -> { + NetUtil.isValidIp(unknownHost, false); + }).isInstanceOf(RuntimeException.class).hasMessageContaining("UnknownHostException"); + + } + +} diff --git a/common/src/test/java/io/seata/common/util/NumberUtilsTest.java b/common/src/test/java/io/seata/common/util/NumberUtilsTest.java new file mode 100644 index 0000000..44a433e --- /dev/null +++ b/common/src/test/java/io/seata/common/util/NumberUtilsTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author caioguedes + */ +public class NumberUtilsTest { + + @Test + public void testToInReturnDefaultValueWithNull() { + Assertions.assertEquals(10, NumberUtils.toInt(null, 10)); + } + + @Test + public void testToInReturnDefaultValueWithFormatIsInvalid() { + Assertions.assertEquals(10, NumberUtils.toInt("nine", 10)); + } + + @Test + public void testToInReturnParsedValue() { + Assertions.assertEquals(10, NumberUtils.toInt("10", 9)); + } +} diff --git a/common/src/test/java/io/seata/common/util/ReflectionUtilTest.java b/common/src/test/java/io/seata/common/util/ReflectionUtilTest.java new file mode 100644 index 0000000..6e5ab28 --- /dev/null +++ b/common/src/test/java/io/seata/common/util/ReflectionUtilTest.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ReflectionUtilTest { + + //Prevent jvm from optimizing final + public static final String testValue = (null != null ? "hello" : "hello"); + + @Test + public void testGetClassByName() throws ClassNotFoundException { + Assertions.assertEquals(String.class, + ReflectionUtil.getClassByName("java.lang.String")); + } + + @Test + public void testGetFieldValue() throws + NoSuchFieldException, IllegalAccessException { + Assertions.assertEquals("d", + ReflectionUtil.getFieldValue(new DurationUtil(), "DAY_UNIT")); + + Assertions.assertThrows(NoSuchFieldException.class, + () -> ReflectionUtil.getFieldValue(new Object(), "A1B2C3")); + } + + @Test + public void testInvokeMethod() throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Assertions.assertEquals(0, ReflectionUtil.invokeMethod("", "length")); + Assertions.assertEquals(3, + ReflectionUtil.invokeMethod("foo", "length")); + + Assertions.assertThrows(NoSuchMethodException.class, + () -> ReflectionUtil.invokeMethod("", "size")); + } + + @Test + public void testInvokeMethod2() throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Assertions.assertEquals(0, ReflectionUtil + .invokeMethod("", "length", null, null)); + Assertions.assertEquals(3, ReflectionUtil + .invokeMethod("foo", "length", null, null)); + + Assertions.assertThrows(NoSuchMethodException.class, () -> ReflectionUtil + .invokeMethod("", "size", null, null)); + } + + @Test + public void testInvokeMethod3() throws NoSuchMethodException, + IllegalAccessException, InvocationTargetException { + Assertions.assertEquals("0", ReflectionUtil.invokeStaticMethod( + String.class, "valueOf", + new Class[]{int.class}, new Object[]{0})); + Assertions.assertEquals("123", ReflectionUtil.invokeStaticMethod( + String.class, "valueOf", + new Class[]{int.class}, new Object[]{123})); + + Assertions.assertThrows(NoSuchMethodException.class, () -> ReflectionUtil + .invokeStaticMethod(String.class, "size", null, null)); + } + + @Test + public void testGetMethod() throws NoSuchMethodException { + Assertions.assertEquals("public int java.lang.String.length()", + ReflectionUtil.getMethod(String.class, "length", null) + .toString()); + Assertions.assertEquals("public char java.lang.String.charAt(int)", + ReflectionUtil.getMethod(String.class, "charAt", + new Class[]{int.class}).toString()); + + Assertions.assertThrows(NoSuchMethodException.class, + () -> ReflectionUtil.getMethod(String.class, "size", null)); + } + + @Test + public void testGetInterfaces() { + Assertions.assertArrayEquals(new Object[]{Serializable.class}, + ReflectionUtil.getInterfaces(Serializable.class).toArray()); + + Assertions.assertArrayEquals(new Object[]{ + Serializable.class, Comparable.class, CharSequence.class}, + ReflectionUtil.getInterfaces(String.class).toArray()); + } + + @Test + public void testModifyStaticFinalField() throws NoSuchFieldException, IllegalAccessException { + Assertions.assertEquals("hello", testValue); + ReflectionUtil.modifyStaticFinalField(ReflectionUtilTest.class, "testValue", "hello world"); + Assertions.assertEquals("hello world", testValue); + } +} diff --git a/common/src/test/java/io/seata/common/util/SizeUtilTest.java b/common/src/test/java/io/seata/common/util/SizeUtilTest.java new file mode 100644 index 0000000..5fd545c --- /dev/null +++ b/common/src/test/java/io/seata/common/util/SizeUtilTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class SizeUtilTest { + @Test + void size2Long() { + assertThat(SizeUtil.size2Long("2k")).isEqualTo(2L * 1024); + assertThat(SizeUtil.size2Long("2m")).isEqualTo(2L * 1024 * 1024); + assertThat(SizeUtil.size2Long("2G")).isEqualTo(2L * 1024 * 1024 * 1024); + assertThat(SizeUtil.size2Long("2t")).isEqualTo(2L * 1024 * 1024 * 1024 * 1024); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/util/StringFormatUtilsTest.java b/common/src/test/java/io/seata/common/util/StringFormatUtilsTest.java new file mode 100644 index 0000000..276d1dd --- /dev/null +++ b/common/src/test/java/io/seata/common/util/StringFormatUtilsTest.java @@ -0,0 +1,35 @@ +package io.seata.common.util; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class StringFormatUtilsTest { + + @Test + void camelToUnderline() { + assertThat(StringFormatUtils.camelToUnderline(null)).isEqualTo(""); + assertThat(StringFormatUtils.camelToUnderline(" ")).isEqualTo(""); + assertThat(StringFormatUtils.camelToUnderline("abcDefGh")).isEqualTo("abc_def_gh"); + } + + @Test + void underlineToCamel() { + assertThat(StringFormatUtils.underlineToCamel(null)).isEqualTo(""); + assertThat(StringFormatUtils.underlineToCamel(" ")).isEqualTo(""); + assertThat(StringFormatUtils.underlineToCamel("abc_def_gh")).isEqualTo("abcDefGh"); + } + + @Test + void minusToCamel() { + assertThat(StringFormatUtils.minusToCamel(null)).isEqualTo(""); + assertThat(StringFormatUtils.minusToCamel(" ")).isEqualTo(""); + assertThat(StringFormatUtils.minusToCamel("abc-def-gh")).isEqualTo("abcDefGh"); + } + + @Test + void dotToCamel() { + assertThat(StringFormatUtils.dotToCamel(null)).isEqualTo(""); + assertThat(StringFormatUtils.dotToCamel(" ")).isEqualTo(""); + assertThat(StringFormatUtils.dotToCamel("abc.def.gh")).isEqualTo("abcDefGh"); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/seata/common/util/StringUtilsTest.java b/common/src/test/java/io/seata/common/util/StringUtilsTest.java new file mode 100644 index 0000000..c83ed3e --- /dev/null +++ b/common/src/test/java/io/seata/common/util/StringUtilsTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.common.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import io.seata.common.Constants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * The type String utils test. + * + * @author Otis.z + * @author Geng Zhang + */ +public class StringUtilsTest { + + /** + * Test is empty. + */ + @Test + public void testIsNullOrEmpty() { + + assertThat(StringUtils.isNullOrEmpty(null)).isTrue(); + assertThat(StringUtils.isNullOrEmpty("abc")).isFalse(); + assertThat(StringUtils.isNullOrEmpty("")).isTrue(); + assertThat(StringUtils.isNullOrEmpty(" ")).isFalse(); + } + + @Test + public void testInputStream2String() throws IOException { + assertNull(StringUtils.inputStream2String(null)); + String data = "abc\n" + + ":\"klsdf\n" + + "2ks,x:\".,-3sd˚ø≤ø¬≥"; + ByteArrayInputStream inputStream = new ByteArrayInputStream(data.getBytes(Constants.DEFAULT_CHARSET)); + assertThat(StringUtils.inputStream2String(inputStream)).isEqualTo(data); + } + + @Test + void inputStream2Bytes() { + assertNull(StringUtils.inputStream2Bytes(null)); + String data = "abc\n" + + ":\"klsdf\n" + + "2ks,x:\".,-3sd˚ø≤ø¬≥"; + byte[] bs = data.getBytes(Constants.DEFAULT_CHARSET); + ByteArrayInputStream inputStream = new ByteArrayInputStream(data.getBytes(Constants.DEFAULT_CHARSET)); + assertThat(StringUtils.inputStream2Bytes(inputStream)).isEqualTo(bs); + } + + @Test + void testEquals() { + Assertions.assertTrue(StringUtils.equals("1", "1")); + Assertions.assertFalse(StringUtils.equals("1", "2")); + Assertions.assertFalse(StringUtils.equals(null, "1")); + Assertions.assertFalse(StringUtils.equals("1", null)); + Assertions.assertFalse(StringUtils.equals("", null)); + Assertions.assertFalse(StringUtils.equals(null, "")); + } + + @Test + void testEqualsIgnoreCase() { + Assertions.assertTrue(StringUtils.equalsIgnoreCase("a", "a")); + Assertions.assertTrue(StringUtils.equalsIgnoreCase("a", "A")); + Assertions.assertTrue(StringUtils.equalsIgnoreCase("A", "a")); + Assertions.assertFalse(StringUtils.equalsIgnoreCase("1", "2")); + Assertions.assertFalse(StringUtils.equalsIgnoreCase(null, "1")); + Assertions.assertFalse(StringUtils.equalsIgnoreCase("1", null)); + Assertions.assertFalse(StringUtils.equalsIgnoreCase("", null)); + Assertions.assertFalse(StringUtils.equalsIgnoreCase(null, "")); + } + + @Test + void testCycleDependency() throws StackOverflowError{ + StringUtils.toString(CycleDependency.A); + } + + static class CycleDependency { + public static final CycleDependency A = new CycleDependency("a"); + public static final CycleDependency B = new CycleDependency("b"); + + private String s; + private CycleDependency(String s) { + this.s = s; + } + + @Override + public String toString() { + return "{" + + "s='" + s + '\'' + + '}'; + } + } +} diff --git a/common/src/test/resources/META-INF/seata/frenchhello/io.seata.common.loader.Hello b/common/src/test/resources/META-INF/seata/frenchhello/io.seata.common.loader.Hello new file mode 100644 index 0000000..76d8cff --- /dev/null +++ b/common/src/test/resources/META-INF/seata/frenchhello/io.seata.common.loader.Hello @@ -0,0 +1,19 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +io.seata.common.loader.EnglishHello +io.seata.common.loader.LatinHello \ No newline at end of file diff --git a/common/src/test/resources/META-INF/seata/io.seata.common.loader.Hello b/common/src/test/resources/META-INF/seata/io.seata.common.loader.Hello new file mode 100644 index 0000000..81f68fa --- /dev/null +++ b/common/src/test/resources/META-INF/seata/io.seata.common.loader.Hello @@ -0,0 +1,19 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.common.loader.FrenchHello +io.seata.common.loader.EnglishHello +io.seata.common.loader.LatinHello \ No newline at end of file diff --git a/common/src/test/resources/META-INF/services/io.seata.common.loader.Hello b/common/src/test/resources/META-INF/services/io.seata.common.loader.Hello new file mode 100644 index 0000000..693b332 --- /dev/null +++ b/common/src/test/resources/META-INF/services/io.seata.common.loader.Hello @@ -0,0 +1,19 @@ +# +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +io.seata.common.loader.ChineseHello \ No newline at end of file diff --git a/compressor/pom.xml b/compressor/pom.xml new file mode 100644 index 0000000..a4650e0 --- /dev/null +++ b/compressor/pom.xml @@ -0,0 +1,41 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-compressor + pom + seata-compressor ${project.version} + + + seata-compressor-all + seata-compressor-gzip + seata-compressor-zip + seata-compressor-7z + seata-compressor-bzip2 + seata-compressor-lz4 + seata-compressor-deflater + + + + \ No newline at end of file diff --git a/compressor/seata-compressor-7z/pom.xml b/compressor/seata-compressor-7z/pom.xml new file mode 100644 index 0000000..158a332 --- /dev/null +++ b/compressor/seata-compressor-7z/pom.xml @@ -0,0 +1,47 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-7z + jar + seata-compressor-7z ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + org.tukaani + xz + + + org.apache.commons + commons-compress + + + + + diff --git a/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZCompressor.java b/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZCompressor.java new file mode 100644 index 0000000..528382d --- /dev/null +++ b/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZCompressor.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.sevenz; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * the SevenZ Compressor + * + * @author ph3636 + */ +@LoadLevel(name = "SEVENZ") +public class SevenZCompressor implements Compressor { + + @Override + public byte[] compress(byte[] bytes) { + return SevenZUtil.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return SevenZUtil.decompress(bytes); + } + +} diff --git a/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZUtil.java b/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZUtil.java new file mode 100644 index 0000000..81d529a --- /dev/null +++ b/compressor/seata-compressor-7z/src/main/java/io/seata/compressor/sevenz/SevenZUtil.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.sevenz; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; +import org.apache.commons.compress.archivers.sevenz.SevenZFile; +import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +/** + * the SevenZ Util + * + * @author ph3636 + */ +public class SevenZUtil { + + private static final int BUFFER_SIZE = 8192; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(); + try (SevenZOutputFile z7z = new SevenZOutputFile(channel)) { + SevenZArchiveEntry entry = new SevenZArchiveEntry(); + entry.setName("sevenZip"); + entry.setSize(bytes.length); + z7z.putArchiveEntry(entry); + z7z.write(bytes); + z7z.closeArchiveEntry(); + z7z.finish(); + return channel.array(); + } catch (IOException e) { + throw new RuntimeException("SevenZ compress error", e); + } + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(bytes); + try (SevenZFile sevenZFile = new SevenZFile(channel)) { + byte[] buffer = new byte[BUFFER_SIZE]; + while (sevenZFile.getNextEntry() != null) { + int n; + while ((n = sevenZFile.read(buffer)) > -1) { + out.write(buffer, 0, n); + } + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("SevenZ decompress error", e); + } + } +} diff --git a/compressor/seata-compressor-7z/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-7z/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..67539a8 --- /dev/null +++ b/compressor/seata-compressor-7z/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.sevenz.SevenZCompressor \ No newline at end of file diff --git a/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZCompressorTest.java b/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZCompressorTest.java new file mode 100644 index 0000000..fa19bd8 --- /dev/null +++ b/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZCompressorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.sevenz; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the SevenZ Compressor test + * + * @author ph3636 + */ +public class SevenZCompressorTest { + + @Test + public void testCompressAndDecompress() { + SevenZCompressor compressor = new SevenZCompressor(); + byte[] bytes = "aa".getBytes(); + bytes = compressor.compress(bytes); + bytes = compressor.decompress(bytes); + Assertions.assertEquals(new String(bytes), "aa"); + } +} diff --git a/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZUtilTest.java b/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZUtilTest.java new file mode 100644 index 0000000..9a6c7f0 --- /dev/null +++ b/compressor/seata-compressor-7z/src/test/java/io/seata/compressor/sevenz/SevenZUtilTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.sevenz; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +/** + * the SevenZ Util test + * + * @author ph3636 + */ +public class SevenZUtilTest { + + @Test + public void test_compress() { + Assertions.assertThrows(NullPointerException.class, () -> { + SevenZUtil.compress(null); + }); + } + + @Test + public void test_decompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + SevenZUtil.decompress(null); + }); + } +} diff --git a/compressor/seata-compressor-all/pom.xml b/compressor/seata-compressor-all/pom.xml new file mode 100644 index 0000000..9b15fd0 --- /dev/null +++ b/compressor/seata-compressor-all/pom.xml @@ -0,0 +1,62 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-all + seata-compressor-all ${project.version} + + + + ${project.groupId} + seata-compressor-gzip + ${project.version} + + + ${project.groupId} + seata-compressor-7z + ${project.version} + + + ${project.groupId} + seata-compressor-bzip2 + ${project.version} + + + ${project.groupId} + seata-compressor-zip + ${project.version} + + + ${project.groupId} + seata-compressor-lz4 + ${project.version} + + + ${project.groupId} + seata-compressor-deflater + ${project.version} + + + + \ No newline at end of file diff --git a/compressor/seata-compressor-bzip2/pom.xml b/compressor/seata-compressor-bzip2/pom.xml new file mode 100644 index 0000000..9939e3a --- /dev/null +++ b/compressor/seata-compressor-bzip2/pom.xml @@ -0,0 +1,43 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-bzip2 + jar + seata-compressor-bzip2 ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + org.apache.ant + ant + + + + + diff --git a/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Compressor.java b/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Compressor.java new file mode 100644 index 0000000..e6cdc86 --- /dev/null +++ b/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Compressor.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.bzip2; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * the BZip2 Compressor + * + * @author ph3636 + */ +@LoadLevel(name = "BZIP2") +public class BZip2Compressor implements Compressor { + + @Override + public byte[] compress(byte[] bytes) { + return BZip2Util.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return BZip2Util.decompress(bytes); + } + +} diff --git a/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Util.java b/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Util.java new file mode 100644 index 0000000..0482b0a --- /dev/null +++ b/compressor/seata-compressor-bzip2/src/main/java/io/seata/compressor/bzip2/BZip2Util.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.bzip2; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.apache.tools.bzip2.CBZip2InputStream; +import org.apache.tools.bzip2.CBZip2OutputStream; + +/** + * the BZip2 Util + * + * @author ph3636 + */ +public class BZip2Util { + + private static final int BUFFER_SIZE = 8192; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try (CBZip2OutputStream bzip2 = new CBZip2OutputStream(bos)) { + bzip2.write(bytes); + bzip2.finish(); + return bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("BZip2 compress error", e); + } + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + try (CBZip2InputStream bzip2 = new CBZip2InputStream(bis)) { + byte[] buffer = new byte[BUFFER_SIZE]; + int n; + while ((n = bzip2.read(buffer)) > -1) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("BZip2 decompress error", e); + } + } +} diff --git a/compressor/seata-compressor-bzip2/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-bzip2/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..4c40168 --- /dev/null +++ b/compressor/seata-compressor-bzip2/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.bzip2.BZip2Compressor \ No newline at end of file diff --git a/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2CompressorTest.java b/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2CompressorTest.java new file mode 100644 index 0000000..258eed7 --- /dev/null +++ b/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2CompressorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.bzip2; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the BZip2 Compressor test + * + * @author ph3636 + */ +public class BZip2CompressorTest { + + @Test + public void testCompressAndDecompress() { + BZip2Compressor compressor = new BZip2Compressor(); + byte[] bytes = "aa".getBytes(); + bytes = compressor.compress(bytes); + bytes = compressor.decompress(bytes); + Assertions.assertEquals(new String(bytes), "aa"); + } +} diff --git a/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2UtilTest.java b/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2UtilTest.java new file mode 100644 index 0000000..25b10ef --- /dev/null +++ b/compressor/seata-compressor-bzip2/src/test/java/io/seata/compressor/bzip2/BZip2UtilTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.bzip2; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the BZip2 Util test + * + * @author ph3636 + */ +public class BZip2UtilTest { + + @Test + public void test_compress() { + Assertions.assertThrows(NullPointerException.class, () -> { + BZip2Util.compress(null); + }); + } + + @Test + public void test_decompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + BZip2Util.decompress(null); + }); + } +} diff --git a/compressor/seata-compressor-deflater/pom.xml b/compressor/seata-compressor-deflater/pom.xml new file mode 100644 index 0000000..0db0ac7 --- /dev/null +++ b/compressor/seata-compressor-deflater/pom.xml @@ -0,0 +1,39 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-deflater + jar + seata-compressor-deflater ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + + + \ No newline at end of file diff --git a/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterCompressor.java b/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterCompressor.java new file mode 100644 index 0000000..87afbcf --- /dev/null +++ b/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterCompressor.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.deflater; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * @author dongzl + */ +@LoadLevel(name = "DEFLATER") +public class DeflaterCompressor implements Compressor { + + @Override + public byte[] compress(byte[] bytes) { + return DeflaterUtil.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return DeflaterUtil.decompress(bytes); + } + +} diff --git a/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterUtil.java b/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterUtil.java new file mode 100644 index 0000000..4bbdcca --- /dev/null +++ b/compressor/seata-compressor-deflater/src/main/java/io/seata/compressor/deflater/DeflaterUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.deflater; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * @author dongzl + */ +public class DeflaterUtil { + + private DeflaterUtil() { + + } + + private static final int BUFFER_SIZE = 8192; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + int lenght = 0; + Deflater deflater = new Deflater(); + deflater.setInput(bytes); + deflater.finish(); + byte[] outputBytes = new byte[BUFFER_SIZE]; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + while (!deflater.finished()) { + lenght = deflater.deflate(outputBytes); + bos.write(outputBytes, 0, lenght); + } + deflater.end(); + return bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Deflater compress error", e); + } + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + int length = 0; + Inflater inflater = new Inflater(); + inflater.setInput(bytes); + byte[] outputBytes = new byte[BUFFER_SIZE]; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream();) { + while (!inflater.finished()) { + length = inflater.inflate(outputBytes); + if (length == 0) { + break; + } + bos.write(outputBytes, 0, length); + } + inflater.end(); + return bos.toByteArray(); + } catch (Exception e) { + throw new RuntimeException("Deflater decompress error", e); + } + } + +} diff --git a/compressor/seata-compressor-deflater/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-deflater/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..ad4c138 --- /dev/null +++ b/compressor/seata-compressor-deflater/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.deflater.DeflaterCompressor \ No newline at end of file diff --git a/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterCompressorTest.java b/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterCompressorTest.java new file mode 100644 index 0000000..7e1d297 --- /dev/null +++ b/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterCompressorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.deflater; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author dongzl + */ +public class DeflaterCompressorTest { + + @Test + public void testCompressAndDecompress() { + DeflaterCompressor compressor = new DeflaterCompressor(); + byte[] bytes = "seata".getBytes(); + bytes = compressor.compress(bytes); + bytes = compressor.decompress(bytes); + Assertions.assertEquals(new String(bytes), "seata"); + } +} diff --git a/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterUtilTest.java b/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterUtilTest.java new file mode 100644 index 0000000..1fc8032 --- /dev/null +++ b/compressor/seata-compressor-deflater/src/test/java/io/seata/compressor/deflater/DeflaterUtilTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.deflater; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author dongzl + */ +public class DeflaterUtilTest { + + @Test + public void test_compress() { + Assertions.assertThrows(NullPointerException.class, () -> { + DeflaterUtil.compress(null); + }); + } + + @Test + public void test_decompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + DeflaterUtil.decompress(null); + }); + } + + @Test + public void test_compressEqualDecompress() { + byte[] compress = DeflaterUtil.compress("seata".getBytes()); + byte[] decompress = DeflaterUtil.decompress(compress); + Assertions.assertEquals("seata", new String(decompress)); + } + +} diff --git a/compressor/seata-compressor-gzip/pom.xml b/compressor/seata-compressor-gzip/pom.xml new file mode 100644 index 0000000..7f419ac --- /dev/null +++ b/compressor/seata-compressor-gzip/pom.xml @@ -0,0 +1,39 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-gzip + jar + seata-compressor-gzip ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + + + \ No newline at end of file diff --git a/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipCompressor.java b/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipCompressor.java new file mode 100644 index 0000000..952a50a --- /dev/null +++ b/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipCompressor.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.gzip; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * @author jsbxyyx + */ +@LoadLevel(name = "GZIP") +public class GzipCompressor implements Compressor { + + @Override + public byte[] compress(byte[] bytes) { + return GzipUtil.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return GzipUtil.decompress(bytes); + } + +} diff --git a/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipUtil.java b/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipUtil.java new file mode 100644 index 0000000..916d248 --- /dev/null +++ b/compressor/seata-compressor-gzip/src/main/java/io/seata/compressor/gzip/GzipUtil.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.gzip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * @author jsbxyyx + */ +public class GzipUtil { + + private GzipUtil() { + + } + + private static final int BUFFER_SIZE = 8192; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(out)) { + gzip.write(bytes); + gzip.flush(); + gzip.finish(); + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("gzip compress error", e); + } + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPInputStream gunzip = new GZIPInputStream(new ByteArrayInputStream(bytes))) { + byte[] buffer = new byte[BUFFER_SIZE]; + int n; + while ((n = gunzip.read(buffer)) > -1) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("gzip decompress error", e); + } + } + +} diff --git a/compressor/seata-compressor-gzip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-gzip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..fa77cad --- /dev/null +++ b/compressor/seata-compressor-gzip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.gzip.GzipCompressor \ No newline at end of file diff --git a/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipCompressorTest.java b/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipCompressorTest.java new file mode 100644 index 0000000..a9c5a21 --- /dev/null +++ b/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipCompressorTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.gzip; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author jsbxyyx + */ +public class GzipCompressorTest { + + @Test + public void testCompressAndDecompress() { + GzipCompressor compressor = new GzipCompressor(); + byte[] bytes = "aa".getBytes(); + bytes = compressor.compress(bytes); + bytes = compressor.decompress(bytes); + Assertions.assertEquals(new String(bytes), "aa"); + } +} diff --git a/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipUtilTest.java b/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipUtilTest.java new file mode 100644 index 0000000..674f33b --- /dev/null +++ b/compressor/seata-compressor-gzip/src/test/java/io/seata/compressor/gzip/GzipUtilTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.gzip; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.zip.GZIPInputStream; + +/** + * @author jsbxyyx + */ +public class GzipUtilTest { + + @Test + public void test_compress() { + Assertions.assertThrows(NullPointerException.class, () -> { + GzipUtil.compress(null); + }); + + byte[] compress = GzipUtil.compress("aa".getBytes()); + int head = ((int) compress[0] & 0xff) | ((compress[1] << 8) & 0xff00); + Assertions.assertEquals(GZIPInputStream.GZIP_MAGIC, head); + } + + @Test + public void test_decompress() { + + Assertions.assertThrows(NullPointerException.class, () -> { + GzipUtil.decompress(null); + }); + + Assertions.assertThrows(RuntimeException.class, () -> { + GzipUtil.decompress(new byte[0]); + }); + + Assertions.assertThrows(RuntimeException.class, () -> { + byte[] bytes = {0x1, 0x2}; + GzipUtil.decompress(bytes); + }); + + } + + @Test + public void test_compressEqualDecompress() { + + byte[] compress = GzipUtil.compress("aa".getBytes()); + + byte[] decompress = GzipUtil.decompress(compress); + + Assertions.assertEquals("aa", new String(decompress)); + } + +} diff --git a/compressor/seata-compressor-lz4/pom.xml b/compressor/seata-compressor-lz4/pom.xml new file mode 100644 index 0000000..408ccc0 --- /dev/null +++ b/compressor/seata-compressor-lz4/pom.xml @@ -0,0 +1,42 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-lz4 + jar + seata-compressor-lz4 ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + org.lz4 + lz4-java + + + + diff --git a/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Compressor.java b/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Compressor.java new file mode 100644 index 0000000..dfac659 --- /dev/null +++ b/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Compressor.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.lz4; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * the Lz4 Compressor + * + * @author diguage + */ +@LoadLevel(name = "LZ4") +public class Lz4Compressor implements Compressor { + @Override + public byte[] compress(byte[] bytes) { + return Lz4Util.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return Lz4Util.decompress(bytes); + } +} diff --git a/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Util.java b/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Util.java new file mode 100644 index 0000000..4df9f30 --- /dev/null +++ b/compressor/seata-compressor-lz4/src/main/java/io/seata/compressor/lz4/Lz4Util.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.lz4; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * the Lz4 Util + * + * @author diguage + */ +public class Lz4Util { + private static final Logger LOGGER = LoggerFactory.getLogger(Lz4Util.class); + private static final int ARRAY_SIZE = 1024; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (LZ4BlockOutputStream lz4BlockOutputStream + = new LZ4BlockOutputStream(outputStream, ARRAY_SIZE, compressor)) { + lz4BlockOutputStream.write(bytes); + } catch (IOException e) { + LOGGER.error("compress bytes error", e); + } + return outputStream.toByteArray(); + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(ARRAY_SIZE); + + LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + try (LZ4BlockInputStream decompressedInputStream + = new LZ4BlockInputStream(inputStream, decompressor)) { + int count; + byte[] buffer = new byte[ARRAY_SIZE]; + while ((count = decompressedInputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, count); + } + } catch (IOException e) { + LOGGER.error("decompress bytes error", e); + } + return outputStream.toByteArray(); + } +} diff --git a/compressor/seata-compressor-lz4/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-lz4/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..adadb02 --- /dev/null +++ b/compressor/seata-compressor-lz4/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.lz4.Lz4Compressor \ No newline at end of file diff --git a/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4CompressorTest.java b/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4CompressorTest.java new file mode 100644 index 0000000..cbc9c7d --- /dev/null +++ b/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4CompressorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.lz4; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author diguage + */ + +public class Lz4CompressorTest { + @Test + public void testCompressAndDecompress() { + Lz4Compressor compressor = new Lz4Compressor(); + String content = "a0123456789"; + byte[] bytes = content.getBytes(); + bytes = compressor.compress(bytes); + byte[] result = compressor.decompress(bytes); + Assertions.assertEquals(new String(result), content); + } +} \ No newline at end of file diff --git a/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4UtilTest.java b/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4UtilTest.java new file mode 100644 index 0000000..2f8497f --- /dev/null +++ b/compressor/seata-compressor-lz4/src/test/java/io/seata/compressor/lz4/Lz4UtilTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.lz4; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author diguage + */ +class Lz4UtilTest { + @Test + public void testCompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + Lz4Util.compress(null); + }); + } + + @Test + public void testDecompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + Lz4Util.decompress(null); + }); + } + +} \ No newline at end of file diff --git a/compressor/seata-compressor-zip/pom.xml b/compressor/seata-compressor-zip/pom.xml new file mode 100644 index 0000000..7e195ef --- /dev/null +++ b/compressor/seata-compressor-zip/pom.xml @@ -0,0 +1,39 @@ + + + + + io.seata + seata-compressor + ${revision} + + 4.0.0 + seata-compressor-zip + jar + seata-compressor-zip ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + + + diff --git a/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipCompressor.java b/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipCompressor.java new file mode 100644 index 0000000..28f6e65 --- /dev/null +++ b/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipCompressor.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.zip; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.Compressor; + +/** + * the Zip Compressor + * + * @author ph3636 + */ +@LoadLevel(name = "ZIP") +public class ZipCompressor implements Compressor { + + @Override + public byte[] compress(byte[] bytes) { + return ZipUtil.compress(bytes); + } + + @Override + public byte[] decompress(byte[] bytes) { + return ZipUtil.decompress(bytes); + } + +} diff --git a/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipUtil.java b/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipUtil.java new file mode 100644 index 0000000..ae788ee --- /dev/null +++ b/compressor/seata-compressor-zip/src/main/java/io/seata/compressor/zip/ZipUtil.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.zip; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * the Zip Util + * + * @author ph3636 + */ +public class ZipUtil { + + private static final int BUFFER_SIZE = 8192; + + public static byte[] compress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (ZipOutputStream zip = new ZipOutputStream(out)) { + ZipEntry entry = new ZipEntry("zip"); + entry.setSize(bytes.length); + zip.putNextEntry(entry); + zip.write(bytes); + zip.closeEntry(); + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Zip compress error", e); + } + } + + public static byte[] decompress(byte[] bytes) { + if (bytes == null) { + throw new NullPointerException("bytes is null"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(bytes))) { + byte[] buffer = new byte[BUFFER_SIZE]; + while (zip.getNextEntry() != null) { + int n; + while ((n = zip.read(buffer)) > -1) { + out.write(buffer, 0, n); + } + } + return out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Zip decompress error", e); + } + } +} diff --git a/compressor/seata-compressor-zip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor b/compressor/seata-compressor-zip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor new file mode 100644 index 0000000..ac1f679 --- /dev/null +++ b/compressor/seata-compressor-zip/src/main/resources/META-INF/services/io.seata.core.compressor.Compressor @@ -0,0 +1 @@ +io.seata.compressor.zip.ZipCompressor \ No newline at end of file diff --git a/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipCompressorTest.java b/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipCompressorTest.java new file mode 100644 index 0000000..3ab2024 --- /dev/null +++ b/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipCompressorTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.zip; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the Zip Compressor test + * + * @author ph3636 + */ +public class ZipCompressorTest { + + @Test + public void testCompressAndDecompress() { + ZipCompressor compressor = new ZipCompressor(); + byte[] bytes = "aa".getBytes(); + bytes = compressor.compress(bytes); + bytes = compressor.decompress(bytes); + Assertions.assertEquals(new String(bytes), "aa"); + } +} diff --git a/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipUtilTest.java b/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipUtilTest.java new file mode 100644 index 0000000..2fba741 --- /dev/null +++ b/compressor/seata-compressor-zip/src/test/java/io/seata/compressor/zip/ZipUtilTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.compressor.zip; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +/** + * the Zip Util test + * + * @author ph3636 + */ +public class ZipUtilTest { + + @Test + public void test_compress() { + Assertions.assertThrows(NullPointerException.class, () -> { + ZipUtil.compress(null); + }); + } + + @Test + public void test_decompress() { + Assertions.assertThrows(NullPointerException.class, () -> { + ZipUtil.decompress(null); + }); + } +} diff --git a/config/pom.xml b/config/pom.xml new file mode 100644 index 0000000..27ea1f4 --- /dev/null +++ b/config/pom.xml @@ -0,0 +1,40 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-config + pom + seata-config ${project.version} + + seata-config-core + seata-config-custom + seata-config-apollo + seata-config-nacos + seata-config-zk + seata-config-all + seata-config-etcd3 + seata-config-consul + seata-config-spring-cloud + + diff --git a/config/seata-config-all/pom.xml b/config/seata-config-all/pom.xml new file mode 100644 index 0000000..46da42a --- /dev/null +++ b/config/seata-config-all/pom.xml @@ -0,0 +1,56 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-all + seata-config-all ${project.version} + + + + ${project.groupId} + seata-config-apollo + ${project.version} + + + ${project.groupId} + seata-config-zk + ${project.version} + + + ${project.groupId} + seata-config-nacos + ${project.version} + + + ${project.groupId} + seata-config-etcd3 + ${project.version} + + + ${project.groupId} + seata-config-consul + ${project.version} + + + + diff --git a/config/seata-config-apollo/pom.xml b/config/seata-config-apollo/pom.xml new file mode 100644 index 0000000..20a4307 --- /dev/null +++ b/config/seata-config-apollo/pom.xml @@ -0,0 +1,40 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-apollo + seata-config-apollo ${project.version} + + + + io.seata + seata-config-core + ${project.version} + + + com.ctrip.framework.apollo + apollo-client + + + + diff --git a/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfiguration.java b/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfiguration.java new file mode 100644 index 0000000..a0481ae --- /dev/null +++ b/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfiguration.java @@ -0,0 +1,235 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.apollo; + +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.ConfigService; +import com.ctrip.framework.apollo.enums.PropertyChangeType; +import com.ctrip.framework.apollo.model.ConfigChange; +import io.netty.util.internal.ConcurrentSet; +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.ConfigFuture; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationChangeType; +import io.seata.config.ConfigurationFactory; + +import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; +import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG; + +/** + * The type Apollo configuration. + * + * @author: kl @kailing.pub + */ +public class ApolloConfiguration extends AbstractConfiguration { + + private static final String REGISTRY_TYPE = "apollo"; + private static final String APP_ID = "appId"; + private static final String APOLLO_META = "apolloMeta"; + private static final String APOLLO_SECRET = "apolloAccesskeySecret"; + private static final String APOLLO_CLUSTER = "seata"; + private static final String APOLLO_CONFIG_SERVICE = "apolloConfigService"; + private static final String PROP_APP_ID = "app.id"; + private static final String PROP_APOLLO_META = "apollo.meta"; + private static final String PROP_APOLLO_CONFIG_SERVICE = "apollo.configService"; + private static final String PROP_APOLLO_SECRET = "apollo.accesskey.secret"; + private static final String PROP_APOLLO_CLUSTER = "apollo.cluster"; + private static final String NAMESPACE = "namespace"; + private static final String DEFAULT_NAMESPACE = "application"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static volatile Config config; + private ExecutorService configOperateExecutor; + private static final int CORE_CONFIG_OPERATE_THREAD = 1; + private static final ConcurrentMap> LISTENER_SERVICE_MAP + = new ConcurrentHashMap<>(); + private static final int MAX_CONFIG_OPERATE_THREAD = 2; + private static volatile ApolloConfiguration instance; + + private ApolloConfiguration() { + readyApolloConfig(); + if (config == null) { + synchronized (ApolloConfiguration.class) { + if (config == null) { + config = ConfigService.getConfig(FILE_CONFIG.getConfig(getApolloNamespaceKey(), DEFAULT_NAMESPACE)); + configOperateExecutor = new ThreadPoolExecutor(CORE_CONFIG_OPERATE_THREAD, + MAX_CONFIG_OPERATE_THREAD, Integer.MAX_VALUE, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new NamedThreadFactory("apolloConfigOperate", MAX_CONFIG_OPERATE_THREAD)); + config.addChangeListener(changeEvent -> { + for (String key : changeEvent.changedKeys()) { + if (!LISTENER_SERVICE_MAP.containsKey(key)) { + continue; + } + ConfigChange change = changeEvent.getChange(key); + ConfigurationChangeEvent event = new ConfigurationChangeEvent(key, change.getNamespace(), + change.getOldValue(), change.getNewValue(), getChangeType(change.getChangeType())); + LISTENER_SERVICE_MAP.get(key).forEach(listener -> listener.onProcessEvent(event)); + } + }); + } + } + } + } + + /** + * Gets instance. + * + * @return the instance + */ + public static ApolloConfiguration getInstance() { + if (instance == null) { + synchronized (ApolloConfiguration.class) { + if (instance == null) { + instance = new ApolloConfiguration(); + } + } + } + return instance; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET, + timeoutMills); + configOperateExecutor.submit(() -> { + String result = config.getProperty(dataId, defaultValue); + configFuture.setResult(result); + }); + return (String) configFuture.get(); + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support putConfig"); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + throw new NotSupportYetException("not support removeConfig"); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + LISTENER_SERVICE_MAP.computeIfAbsent(dataId, key -> new ConcurrentSet<>()) + .add(listener); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + configListeners.remove(listener); + } + } + + @Override + public Set getConfigListeners(String dataId) { + return LISTENER_SERVICE_MAP.get(dataId); + } + + private void readyApolloConfig() { + Properties properties = System.getProperties(); + if (!properties.containsKey(PROP_APP_ID)) { + System.setProperty(PROP_APP_ID, FILE_CONFIG.getConfig(getApolloAppIdFileKey())); + } + if (!properties.containsKey(PROP_APOLLO_META)) { + System.setProperty(PROP_APOLLO_META, FILE_CONFIG.getConfig(getApolloMetaFileKey())); + } + if (!properties.containsKey(PROP_APOLLO_SECRET)) { + String secretKey = FILE_CONFIG.getConfig(getApolloSecretFileKey()); + if (!StringUtils.isBlank(secretKey)) { + System.setProperty(PROP_APOLLO_SECRET, secretKey); + } + } + if (!properties.containsKey(APOLLO_CLUSTER)) { + System.setProperty(PROP_APOLLO_CLUSTER, FILE_CONFIG.getConfig(getApolloCluster())); + } + if (!properties.containsKey(APOLLO_CONFIG_SERVICE)) { + System.setProperty(PROP_APOLLO_CONFIG_SERVICE, FILE_CONFIG.getConfig(getApolloConfigService())); + } + } + + @Override + public String getTypeName() { + return REGISTRY_TYPE; + } + + private static String getApolloMetaFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_META); + } + + private static String getApolloSecretFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_SECRET); + } + + private static String getApolloAppIdFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APP_ID); + } + + private static String getApolloNamespaceKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, NAMESPACE); + } + + private static String getApolloCluster() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_CLUSTER); + } + + private static String getApolloConfigService() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_CONFIG, REGISTRY_TYPE, APOLLO_CONFIG_SERVICE); + } + + + private ConfigurationChangeType getChangeType(PropertyChangeType changeType) { + switch (changeType) { + case ADDED: + return ConfigurationChangeType.ADD; + case DELETED: + return ConfigurationChangeType.DELETE; + default: + return ConfigurationChangeType.MODIFY; + } + } +} diff --git a/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfigurationProvider.java b/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfigurationProvider.java new file mode 100644 index 0000000..70205b1 --- /dev/null +++ b/config/seata-config-apollo/src/main/java/io/seata/config/apollo/ApolloConfigurationProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.apollo; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Apollo", order = 1) +public class ApolloConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + return ApolloConfiguration.getInstance(); + } +} diff --git a/config/seata-config-apollo/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-apollo/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..5f4a891 --- /dev/null +++ b/config/seata-config-apollo/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.apollo.ApolloConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-consul/pom.xml b/config/seata-config-consul/pom.xml new file mode 100644 index 0000000..c5c9de6 --- /dev/null +++ b/config/seata-config-consul/pom.xml @@ -0,0 +1,40 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-consul + seata-config-consul ${project.version} + + + + io.seata + seata-config-core + ${project.version} + + + com.ecwid.consul + consul-api + + + + \ No newline at end of file diff --git a/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfiguration.java b/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfiguration.java new file mode 100644 index 0000000..bd2fb0e --- /dev/null +++ b/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfiguration.java @@ -0,0 +1,278 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.consul; + +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import com.ecwid.consul.v1.ConsulClient; +import com.ecwid.consul.v1.QueryParams; +import com.ecwid.consul.v1.Response; +import com.ecwid.consul.v1.kv.model.GetValue; +import com.ecwid.consul.v1.kv.model.PutParams; +import io.netty.util.internal.ConcurrentSet; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.ConfigFuture; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; + +import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; +import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG; + +/** + * The type Consul configuration. + * + * @author xingfudeshi @gmail.com + */ +public class ConsulConfiguration extends AbstractConfiguration { + private volatile static ConsulConfiguration instance; + private volatile static ConsulClient client; + + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String CONFIG_TYPE = "consul"; + private static final String ACL_TOKEN = "aclToken"; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE + + FILE_CONFIG_SPLIT_CHAR; + private static final int THREAD_POOL_NUM = 1; + private static final int MAP_INITIAL_CAPACITY = 8; + private ExecutorService consulNotifierExecutor; + private ConcurrentMap> configListenersMap = new ConcurrentHashMap<>( + MAP_INITIAL_CAPACITY); + + /** + * default watch timeout in second + */ + private static final int DEFAULT_WATCH_TIMEOUT = 60; + private static final long CAS = 0L; + + private ConsulConfiguration() { + consulNotifierExecutor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, Integer.MAX_VALUE, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("consul-config-executor", THREAD_POOL_NUM)); + } + + /** + * get instance + * + * @return instance + */ + public static ConsulConfiguration getInstance() { + if (instance == null) { + synchronized (ConsulConfiguration.class) { + if (instance == null) { + instance = new ConsulConfiguration(); + } + } + } + return instance; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET, + timeoutMills); + consulNotifierExecutor.execute(() -> complete(getConsulClient().getKVValue(dataId, getAclToken()), configFuture)); + return (String) configFuture.get(); + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUT, timeoutMills); + consulNotifierExecutor.execute(() -> complete(getConsulClient().setKVValue(dataId, content, getAclToken(), null), configFuture)); + return (Boolean) configFuture.get(); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUTIFABSENT, + timeoutMills); + consulNotifierExecutor.execute(() -> { + PutParams putParams = new PutParams(); + //Setting CAS to 0 means that this is an atomic operation, created when key does not exist. + putParams.setCas(CAS); + complete(getConsulClient().setKVValue(dataId, content, getAclToken(), putParams), configFuture); + }); + return (Boolean) configFuture.get(); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigFuture.ConfigOperation.REMOVE, timeoutMills); + consulNotifierExecutor.execute(() -> complete(getConsulClient().deleteKVValue(dataId, getAclToken()), configFuture)); + return (Boolean) configFuture.get(); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + ConsulListener consulListener = new ConsulListener(dataId, listener); + configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>()) + .add(consulListener); + + // Start config change listener for the dataId. + consulListener.onProcessEvent(new ConfigurationChangeEvent()); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + ConfigurationChangeListener target; + for (ConfigurationChangeListener entry : configListeners) { + target = ((ConsulListener) entry).getTargetListener(); + if (listener.equals(target)) { + entry.onShutDown(); + configListeners.remove(entry); + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + return configListenersMap.get(dataId); + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + /** + * get consul client + * + * @return client + */ + private static ConsulClient getConsulClient() { + if (client == null) { + synchronized (ConsulConfiguration.class) { + if (client == null) { + String serverAddr = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY); + InetSocketAddress inetSocketAddress = NetUtil.toInetSocketAddress(serverAddr); + client = new ConsulClient(inetSocketAddress.getHostName(), inetSocketAddress.getPort()); + } + } + } + return client; + } + + /** + * get consul acl-token + * + * @return acl-token + */ + private static String getAclToken() { + String aclToken = StringUtils.isNotBlank(System.getProperty(ACL_TOKEN)) ? System.getProperty(ACL_TOKEN) + : FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + ACL_TOKEN); + return StringUtils.isNotBlank(aclToken) ? aclToken : null; + } + + /** + * complete the future + * + * @param response + * @param configFuture + */ + private void complete(Response response, ConfigFuture configFuture) { + if (response != null && response.getValue() != null) { + Object value = response.getValue(); + if (value instanceof GetValue) { + configFuture.setResult(((GetValue) value).getDecodedValue()); + } else { + configFuture.setResult(value); + } + } + } + + /** + * The type Consul listener. + */ + public static class ConsulListener implements ConfigurationChangeListener { + + private final ConfigurationChangeListener listener; + private final String dataId; + private long consulIndex; + private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("consulListener", MAX_LISTENER_THREAD)); + + /** + * Instantiates a new Consul listener. + * + * @param dataId the data id + * @param listener the listener + */ + public ConsulListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + this.consulIndex = getConsulClient().getKVValue(dataId, getAclToken()).getConsulIndex(); + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (listener != null) { + while (true) { + QueryParams queryParams = new QueryParams(DEFAULT_WATCH_TIMEOUT, consulIndex); + Response response = getConsulClient().getKVValue(this.dataId, getAclToken(), queryParams); + Long currentIndex = response.getConsulIndex(); + if (currentIndex != null && currentIndex > consulIndex) { + GetValue getValue = response.getValue(); + consulIndex = currentIndex; + event.setDataId(dataId).setNewValue(getValue.getDecodedValue()); + listener.onChangeEvent(event); + } + } + } + } + + @Override + public ExecutorService getExecutorService() { + return executor; + } + + /** + * Gets target listener. + * + * @return the target listener + */ + public ConfigurationChangeListener getTargetListener() { + return this.listener; + } + } +} diff --git a/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfigurationProvider.java b/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfigurationProvider.java new file mode 100644 index 0000000..7113b89 --- /dev/null +++ b/config/seata-config-consul/src/main/java/io/seata/config/consul/ConsulConfigurationProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.consul; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Consul", order = 1) +public class ConsulConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + return ConsulConfiguration.getInstance(); + } +} diff --git a/config/seata-config-consul/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-consul/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..d4de848 --- /dev/null +++ b/config/seata-config-consul/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.consul.ConsulConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-core/pom.xml b/config/seata-config-core/pom.xml new file mode 100644 index 0000000..f9a2f0e --- /dev/null +++ b/config/seata-config-core/pom.xml @@ -0,0 +1,48 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-core + seata-config-core ${project.version} + + + + ${project.groupId} + seata-common + ${project.version} + + + com.typesafe + config + + + org.yaml + snakeyaml + + + cglib + cglib + + + + diff --git a/config/seata-config-core/src/main/java/io/seata/config/AbstractConfiguration.java b/config/seata-config-core/src/main/java/io/seata/config/AbstractConfiguration.java new file mode 100644 index 0000000..858467f --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/AbstractConfiguration.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.time.Duration; +import io.seata.common.util.DurationUtil; + +/** + * The type Abstract configuration. + * + * @author slievrly + */ +public abstract class AbstractConfiguration implements Configuration { + + /** + * The constant DEFAULT_CONFIG_TIMEOUT. + */ + protected static final long DEFAULT_CONFIG_TIMEOUT = 5 * 1000; + + @Override + public short getShort(String dataId, int defaultValue, long timeoutMills) { + String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills); + return Short.parseShort(result); + } + + @Override + public short getShort(String dataId, short defaultValue) { + return getShort(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public short getShort(String dataId) { + return getShort(dataId, (short) 0); + } + + @Override + public int getInt(String dataId, int defaultValue, long timeoutMills) { + String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills); + return Integer.parseInt(result); + } + + @Override + public int getInt(String dataId, int defaultValue) { + return getInt(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public int getInt(String dataId) { + return getInt(dataId, 0); + } + + @Override + public long getLong(String dataId, long defaultValue, long timeoutMills) { + String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills); + return Long.parseLong(result); + } + + @Override + public long getLong(String dataId, long defaultValue) { + return getLong(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public long getLong(String dataId) { + return getLong(dataId, 0L); + } + + @Override + public Duration getDuration(String dataId) { + return getDuration(dataId, Duration.ZERO); + } + + @Override + public Duration getDuration(String dataId, Duration defaultValue) { + return getDuration(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public Duration getDuration(String dataId, Duration defaultValue, long timeoutMills) { + String result = getConfig(dataId, defaultValue.toMillis() + "ms", timeoutMills); + return DurationUtil.parse(result); + } + + @Override + public boolean getBoolean(String dataId, boolean defaultValue, long timeoutMills) { + String result = getConfig(dataId, String.valueOf(defaultValue), timeoutMills); + return Boolean.parseBoolean(result); + } + + @Override + public boolean getBoolean(String dataId, boolean defaultValue) { + return getBoolean(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public boolean getBoolean(String dataId) { + return getBoolean(dataId, false); + } + + @Override + public String getConfig(String dataId, String defaultValue) { + return getConfig(dataId, defaultValue, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public String getConfig(String dataId, long timeoutMills) { + return getConfig(dataId, null, timeoutMills); + } + + @Override + public String getConfig(String dataId, String content, long timeoutMills) { + return getLatestConfig(dataId, content, timeoutMills); + } + + @Override + public String getConfig(String dataId) { + return getConfig(dataId, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public boolean putConfig(String dataId, String content) { + return putConfig(dataId, content, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content) { + return putConfigIfAbsent(dataId, content, DEFAULT_CONFIG_TIMEOUT); + } + + @Override + public boolean removeConfig(String dataId) { + return removeConfig(dataId, DEFAULT_CONFIG_TIMEOUT); + } + + /** + * Gets type name. + * + * @return the type name + */ + public abstract String getTypeName(); +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigChangeListener.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigChangeListener.java new file mode 100644 index 0000000..d67f2e4 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigChangeListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.util.concurrent.ExecutorService; + +/** + * The interface Config change listener. + * + * @author slievrly + */ +public interface ConfigChangeListener { + + /** + * Gets executor. + * + * @return the executor + */ + ExecutorService getExecutor(); + + /** + * Receive config info. + * + * @param configInfo the config info + */ + void receiveConfigInfo(final String configInfo); +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigFuture.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigFuture.java new file mode 100644 index 0000000..7d2c3a7 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigFuture.java @@ -0,0 +1,207 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + + +import io.seata.common.exception.ShouldNeverHappenException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * The type Config future. + * + * @author slievrly + */ +public class ConfigFuture { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigFuture.class); + private static final long DEFAULT_CONFIG_TIMEOUT = 5 * 1000; + private long timeoutMills; + private long start = System.currentTimeMillis(); + private String dataId; + private String content; + private ConfigOperation operation; + private transient CompletableFuture origin = new CompletableFuture<>(); + + /** + * Instantiates a new Config future. + * + * @param dataId the data id + * @param content the content + * @param operation the operation + */ + public ConfigFuture(String dataId, String content, ConfigOperation operation) { + this(dataId, content, operation, DEFAULT_CONFIG_TIMEOUT); + } + + /** + * Instantiates a new Config future. + * + * @param dataId the data id + * @param content the content + * @param operation the operation + * @param timeoutMills the timeout mills + */ + public ConfigFuture(String dataId, String content, ConfigOperation operation, long timeoutMills) { + this.dataId = dataId; + this.content = content; + this.operation = operation; + this.timeoutMills = timeoutMills; + } + + /** + * Gets timeout mills. + * + * @return the timeout mills + */ + public boolean isTimeout() { + return System.currentTimeMillis() - start > timeoutMills; + } + + /** + * Get object. + * + * @return the object + */ + public Object get() { + return get(this.timeoutMills, TimeUnit.MILLISECONDS); + } + + /** + * Get object. + * + * @param timeout the timeout + * @param unit the unit + * @return the object + */ + public Object get(long timeout, TimeUnit unit) { + this.timeoutMills = unit.toMillis(timeout); + Object result; + try { + result = origin.get(timeout, unit); + } catch (ExecutionException e) { + throw new ShouldNeverHappenException("Should not get results in a multi-threaded environment", e); + } catch (TimeoutException e) { + LOGGER.error("config operation timeout,cost:{} ms,op:{},dataId:{}", System.currentTimeMillis() - start, operation.name(), dataId); + return getFailResult(); + } catch (InterruptedException exx) { + LOGGER.error("config operate interrupted,error:{}", exx.getMessage(), exx); + return getFailResult(); + } + if (operation == ConfigOperation.GET) { + return result == null ? content : result; + } else { + return result == null ? Boolean.FALSE : result; + } + } + + private Object getFailResult() { + if (operation == ConfigOperation.GET) { + return content; + } else { + return Boolean.FALSE; + } + } + + /** + * Sets result. + * + * @param result the result + */ + public void setResult(Object result) { + origin.complete(result); + } + + /** + * Gets data id. + * + * @return the data id + */ + public String getDataId() { + return dataId; + } + + /** + * Sets data id. + * + * @param dataId the data id + */ + public void setDataId(String dataId) { + this.dataId = dataId; + } + + /** + * Gets content. + * + * @return the content + */ + public String getContent() { + return content; + } + + /** + * Sets content. + * + * @param content the content + */ + public void setContent(String content) { + this.content = content; + } + + /** + * Gets operation. + * + * @return the operation + */ + public ConfigOperation getOperation() { + return operation; + } + + /** + * Sets operation. + * + * @param operation the operation + */ + public void setOperation(ConfigOperation operation) { + this.operation = operation; + } + + /** + * The enum Config operation. + */ + public enum ConfigOperation { + /** + * Get config operation. + */ + GET, + /** + * Put config operation. + */ + PUT, + /** + * Putifabsent config operation. + */ + PUTIFABSENT, + /** + * Remove config operation. + */ + REMOVE + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigType.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigType.java new file mode 100644 index 0000000..326a1d4 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigType.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * The enum Config type. + * + * @author slievrly + */ +public enum ConfigType { + /** + * File config type. + */ + File, + /** + * zookeeper config type. + */ + ZK, + /** + * Nacos config type. + */ + Nacos, + /** + * Apollo config type. + */ + Apollo, + /** + * Consul config type + */ + Consul, + /** + * Etcd3 config type + */ + Etcd3, + /** + * spring cloud config type + */ + SpringCloudConfig, + /** + * Custom config type + */ + Custom; + + /** + * Gets type. + * + * @param name the name + * @return the type + */ + public static ConfigType getType(String name) { + for (ConfigType configType : values()) { + if (configType.name().equalsIgnoreCase(name)) { + return configType; + } + } + throw new IllegalArgumentException("not support config type: " + name); + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/Configuration.java b/config/seata-config-core/src/main/java/io/seata/config/Configuration.java new file mode 100644 index 0000000..1a5cc13 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/Configuration.java @@ -0,0 +1,297 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.time.Duration; +import java.util.Set; + +/** + * The interface Configuration. + * + * @author slievrly + */ +public interface Configuration { + + /** + * Gets short. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the short + */ + short getShort(String dataId, int defaultValue, long timeoutMills); + + /** + * Gets short. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the int + */ + short getShort(String dataId, short defaultValue); + + /** + * Gets short. + * + * @param dataId the data id + * @return the int + */ + short getShort(String dataId); + + /** + * Gets int. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the int + */ + int getInt(String dataId, int defaultValue, long timeoutMills); + + /** + * Gets int. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the int + */ + int getInt(String dataId, int defaultValue); + + /** + * Gets int. + * + * @param dataId the data id + * @return the int + */ + int getInt(String dataId); + + /** + * Gets long. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the long + */ + long getLong(String dataId, long defaultValue, long timeoutMills); + + /** + * Gets long. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the long + */ + long getLong(String dataId, long defaultValue); + + /** + * Gets long. + * + * @param dataId the data id + * @return the long + */ + long getLong(String dataId); + + /** + * Gets duration. + * + * @param dataId the data id + * @return the duration + */ + Duration getDuration(String dataId); + + /** + * Gets duration. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the duration + */ + Duration getDuration(String dataId, Duration defaultValue); + + /** + * Gets duration. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return he duration + */ + Duration getDuration(String dataId, Duration defaultValue, long timeoutMills); + + /** + * Gets boolean. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the boolean + */ + boolean getBoolean(String dataId, boolean defaultValue, long timeoutMills); + + /** + * Gets boolean. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the boolean + */ + boolean getBoolean(String dataId, boolean defaultValue); + + /** + * Gets boolean. + * + * @param dataId the data id + * @return the boolean + */ + boolean getBoolean(String dataId); + + /** + * Gets config. + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the config + */ + String getConfig(String dataId, String defaultValue, long timeoutMills); + + /** + * Gets config. + * + * @param dataId the data id + * @param defaultValue the default value + * @return the config + */ + String getConfig(String dataId, String defaultValue); + + /** + * Gets config. + * + * @param dataId the data id + * @param timeoutMills the timeout mills + * @return the config + */ + String getConfig(String dataId, long timeoutMills); + + /** + * Gets config. + * + * @param dataId the data id + * @return the config + */ + String getConfig(String dataId); + + /** + * Put config boolean. + * + * @param dataId the data id + * @param content the content + * @param timeoutMills the timeout mills + * @return the boolean + */ + boolean putConfig(String dataId, String content, long timeoutMills); + + /** + * + * @param dataId the data id + * @param defaultValue the default value + * @param timeoutMills the timeout mills + * @return the Latest config + */ + String getLatestConfig(String dataId, String defaultValue, long timeoutMills); + + /** + * Put config boolean. + * + * @param dataId the data id + * @param content the content + * @return the boolean + */ + boolean putConfig(String dataId, String content); + + /** + * Put config if absent boolean. + * + * @param dataId the data id + * @param content the content + * @param timeoutMills the timeout mills + * @return the boolean + */ + boolean putConfigIfAbsent(String dataId, String content, long timeoutMills); + + /** + * Put config if absent boolean. + * + * @param dataId the data id + * @param content the content + * @return the boolean + */ + boolean putConfigIfAbsent(String dataId, String content); + + /** + * Remove config boolean. + * + * @param dataId the data id + * @param timeoutMills the timeout mills + * @return the boolean + */ + boolean removeConfig(String dataId, long timeoutMills); + + /** + * Remove config boolean. + * + * @param dataId the data id + * @return the boolean + */ + boolean removeConfig(String dataId); + + /** + * Add config listener. + * + * @param dataId the data id + * @param listener the listener + */ + void addConfigListener(String dataId, ConfigurationChangeListener listener); + + /** + * Remove config listener. + * + * @param dataId the data id + * @param listener the listener + */ + void removeConfigListener(String dataId, ConfigurationChangeListener listener); + + /** + * Gets config listeners. + * + * @param dataId the data id + * @return the config listeners + */ + Set getConfigListeners(String dataId); + + /** + * Gets config from sys pro. + * + * @param dataId the data id + * @return the config from sys pro + */ + default String getConfigFromSysPro(String dataId) { + return System.getProperty(dataId); + } + +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationCache.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationCache.java new file mode 100644 index 0000000..470f9b2 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationCache.java @@ -0,0 +1,174 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.config; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.util.DurationUtil; +import io.seata.common.util.StringUtils; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; + +/** + * @author funkye + */ +public class ConfigurationCache implements ConfigurationChangeListener { + + private static final String METHOD_PREFIX = "get"; + + private static final String METHOD_LATEST_CONFIG = METHOD_PREFIX + "LatestConfig"; + + private static final ConcurrentHashMap CONFIG_CACHE = new ConcurrentHashMap<>(); + + private Map> configListenersMap = new HashMap<>(); + + public static void addConfigListener(String dataId, ConfigurationChangeListener... listeners) { + if (StringUtils.isBlank(dataId)) { + return; + } + synchronized (ConfigurationCache.class) { + HashSet listenerHashSet = + getInstance().configListenersMap.computeIfAbsent(dataId, key -> new HashSet<>()); + if (!listenerHashSet.contains(getInstance())) { + ConfigurationFactory.getInstance().addConfigListener(dataId, getInstance()); + listenerHashSet.add(getInstance()); + } + if (null != listeners && listeners.length > 0) { + for (ConfigurationChangeListener listener : listeners) { + if (!listenerHashSet.contains(listener)) { + listenerHashSet.add(listener); + ConfigurationFactory.getInstance().addConfigListener(dataId, listener); + } + } + } + } + } + + public static ConfigurationCache getInstance() { + return ConfigurationCacheInstance.INSTANCE; + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + ObjectWrapper wrapper = CONFIG_CACHE.get(event.getDataId()); + // The wrapper.data only exists in the cache when it is not null. + if (StringUtils.isNotBlank(event.getNewValue())) { + if (wrapper == null) { + CONFIG_CACHE.put(event.getDataId(), new ObjectWrapper(event.getNewValue(), null)); + } else { + Object newValue = new ObjectWrapper(event.getNewValue(), null).convertData(wrapper.getType()); + if (!Objects.equals(wrapper.getData(), newValue)) { + CONFIG_CACHE.put(event.getDataId(), new ObjectWrapper(newValue, wrapper.getType())); + } + } + } else { + CONFIG_CACHE.remove(event.getDataId()); + } + } + + public Configuration proxy(Configuration originalConfiguration) { + return (Configuration)Enhancer.create(Configuration.class, + (MethodInterceptor)(proxy, method, args, methodProxy) -> { + if (method.getName().startsWith(METHOD_PREFIX) + && !method.getName().equalsIgnoreCase(METHOD_LATEST_CONFIG)) { + String rawDataId = (String)args[0]; + ObjectWrapper wrapper = CONFIG_CACHE.get(rawDataId); + String type = method.getName().substring(METHOD_PREFIX.length()); + if (!ObjectWrapper.supportType(type)) { + type = null; + } + if (null == wrapper) { + Object result = method.invoke(originalConfiguration, args); + // The wrapper.data only exists in the cache when it is not null. + if (result != null) { + wrapper = new ObjectWrapper(result, type); + CONFIG_CACHE.put(rawDataId, wrapper); + } + } + return wrapper == null ? null : wrapper.convertData(type); + } + return method.invoke(originalConfiguration, args); + }); + } + + private static class ConfigurationCacheInstance { + private static final ConfigurationCache INSTANCE = new ConfigurationCache(); + } + + public void clear() { + CONFIG_CACHE.clear(); + } + + private static class ObjectWrapper { + + static final String INT = "Int"; + static final String BOOLEAN = "Boolean"; + static final String DURATION = "Duration"; + static final String LONG = "Long"; + static final String SHORT = "Short"; + + private final Object data; + private final String type; + + ObjectWrapper(Object data, String type) { + this.data = data; + this.type = type; + } + + public Object getData() { + return data; + } + + public String getType() { + return type; + } + + public Object convertData(String aType) { + if (data != null && Objects.equals(type, aType)) { + return data; + } + if (data != null) { + if (INT.equals(aType)) { + return Integer.parseInt(data.toString()); + } else if (BOOLEAN.equals(aType)) { + return Boolean.parseBoolean(data.toString()); + } else if (DURATION.equals(aType)) { + return DurationUtil.parse(data.toString()); + } else if (LONG.equals(aType)) { + return Long.parseLong(data.toString()); + } else if (SHORT.equals(aType)) { + return Short.parseShort(data.toString()); + } + return String.valueOf(data); + } + return null; + } + + public static boolean supportType(String type) { + return INT.equalsIgnoreCase(type) + || BOOLEAN.equalsIgnoreCase(type) + || DURATION.equalsIgnoreCase(type) + || LONG.equalsIgnoreCase(type) + || SHORT.equalsIgnoreCase(type); + } + } + +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeEvent.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeEvent.java new file mode 100644 index 0000000..45bc2e4 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeEvent.java @@ -0,0 +1,144 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * The type Configuration change event. + * + * @author slievrly + */ +public class ConfigurationChangeEvent { + + private String dataId; + private String oldValue; + private String newValue; + private String namespace; + private ConfigurationChangeType changeType; + private static final String DEFAULT_NAMESPACE = "DEFAULT"; + + + public ConfigurationChangeEvent(){ + + } + + public ConfigurationChangeEvent(String dataId, String newValue) { + this(dataId, DEFAULT_NAMESPACE, null, newValue, ConfigurationChangeType.MODIFY); + } + + public ConfigurationChangeEvent(String dataId, String namespace, String oldValue, String newValue, + ConfigurationChangeType type) { + this.dataId = dataId; + this.namespace = namespace; + this.oldValue = oldValue; + this.newValue = newValue; + this.changeType = type; + } + + /** + * Gets data id. + * + * @return the data id + */ + public String getDataId() { + return dataId; + } + + /** + * Sets data id. + * + * @param dataId the data id + */ + public ConfigurationChangeEvent setDataId(String dataId) { + this.dataId = dataId; + return this; + } + + /** + * Gets old value. + * + * @return the old value + */ + public String getOldValue() { + return oldValue; + } + + /** + * Sets old value. + * + * @param oldValue the old value + */ + public ConfigurationChangeEvent setOldValue(String oldValue) { + this.oldValue = oldValue; + return this; + } + + /** + * Gets new value. + * + * @return the new value + */ + public String getNewValue() { + return newValue; + } + + /** + * Sets new value. + * + * @param newValue the new value + */ + public ConfigurationChangeEvent setNewValue(String newValue) { + this.newValue = newValue; + return this; + } + + /** + * Gets change type. + * + * @return the change type + */ + public ConfigurationChangeType getChangeType() { + return changeType; + } + + /** + * Sets change type. + * + * @param changeType the change type + */ + public ConfigurationChangeEvent setChangeType(ConfigurationChangeType changeType) { + this.changeType = changeType; + return this; + } + + /** + * Gets namespace. + * + * @return the namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * Sets namespace. + * + * @param namespace the namespace + */ + public ConfigurationChangeEvent setNamespace(String namespace) { + this.namespace = namespace; + return this; + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeListener.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeListener.java new file mode 100644 index 0000000..fd74d45 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeListener.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.seata.common.thread.NamedThreadFactory; + +/** + * The interface Configuration change listener. + * + * @author slievrly + */ +public interface ConfigurationChangeListener { + + /** + * The constant CORE_LISTENER_THREAD. + */ + int CORE_LISTENER_THREAD = 1; + /** + * The constant MAX_LISTENER_THREAD. + */ + int MAX_LISTENER_THREAD = 1; + /** + * The constant EXECUTOR_SERVICE. + */ + ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, + Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("configListenerOperate", MAX_LISTENER_THREAD)); + + /** + * Process. + * + * @param event the event + */ + void onChangeEvent(ConfigurationChangeEvent event); + + /** + * On process event. + * + * @param event the event + */ + default void onProcessEvent(ConfigurationChangeEvent event) { + getExecutorService().submit(() -> { + beforeEvent(); + onChangeEvent(event); + afterEvent(); + }); + } + + /** + * On shut down. + */ + default void onShutDown() { + getExecutorService().shutdownNow(); + } + + /** + * Gets executor service. + * + * @return the executor service + */ + default ExecutorService getExecutorService() { + return EXECUTOR_SERVICE; + } + + /** + * Before event. + */ + default void beforeEvent() { + + } + + /** + * After event. + */ + default void afterEvent() { + + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeType.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeType.java new file mode 100644 index 0000000..d0ca07f --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationChangeType.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * The enum Configuration change type. + * + * @author slievrly + */ +public enum ConfigurationChangeType { + /** + * Add configuration change type. + */ + ADD, + /** + * Modify configuration change type. + */ + MODIFY, + /** + * Delete configuration change type. + */ + DELETE +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationFactory.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationFactory.java new file mode 100644 index 0000000..957e9fc --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationFactory.java @@ -0,0 +1,157 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.util.Objects; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.common.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Configuration factory. + * + * @author slievrly + * @author Geng Zhang + */ +public final class ConfigurationFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFactory.class); + + private static final String REGISTRY_CONF_DEFAULT = "registry"; + private static final String ENV_SYSTEM_KEY = "SEATA_ENV"; + public static final String ENV_PROPERTY_KEY = "seataEnv"; + + private static final String SYSTEM_PROPERTY_SEATA_CONFIG_NAME = "seata.config.name"; + + private static final String ENV_SEATA_CONFIG_NAME = "SEATA_CONFIG_NAME"; + + public static Configuration CURRENT_FILE_INSTANCE; + + static { + load(); + } + + private static void load() { + String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); + if (seataConfigName == null) { + seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME); + } + if (seataConfigName == null) { + seataConfigName = REGISTRY_CONF_DEFAULT; + } + String envValue = System.getProperty(ENV_PROPERTY_KEY); + if (envValue == null) { + envValue = System.getenv(ENV_SYSTEM_KEY); + } + Configuration configuration = (envValue == null) ? new FileConfiguration(seataConfigName, + false) : new FileConfiguration(seataConfigName + "-" + envValue, false); + Configuration extConfiguration = null; + try { + extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("load Configuration:{}", extConfiguration == null ? configuration.getClass().getSimpleName() + : extConfiguration.getClass().getSimpleName()); + } + } catch (EnhancedServiceNotFoundException ignore) { + + } catch (Exception e) { + LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); + } + CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration; + } + + private static final String NAME_KEY = "name"; + private static final String FILE_TYPE = "file"; + + private static volatile Configuration instance = null; + + /** + * Gets instance. + * + * @return the instance + */ + public static Configuration getInstance() { + if (instance == null) { + synchronized (Configuration.class) { + if (instance == null) { + instance = buildConfiguration(); + } + } + } + return instance; + } + + private static Configuration buildConfiguration() { + String configTypeName = CURRENT_FILE_INSTANCE.getConfig( + ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + + ConfigurationKeys.FILE_ROOT_TYPE); + + if (StringUtils.isBlank(configTypeName)) { + throw new NotSupportYetException("config type can not be null"); + } + ConfigType configType = ConfigType.getType(configTypeName); + + Configuration extConfiguration = null; + Configuration configuration; + if (ConfigType.File == configType) { + String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, + ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY); + String name = CURRENT_FILE_INSTANCE.getConfig(pathDataId); + configuration = new FileConfiguration(name); + try { + extConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("load Configuration:{}", extConfiguration == null + ? configuration.getClass().getSimpleName() : extConfiguration.getClass().getSimpleName()); + } + } catch (EnhancedServiceNotFoundException ignore) { + + } catch (Exception e) { + LOGGER.error("failed to load extConfiguration:{}", e.getMessage(), e); + } + } else { + configuration = EnhancedServiceLoader + .load(ConfigurationProvider.class, Objects.requireNonNull(configType).name()).provide(); + } + try { + Configuration configurationCache; + if (null != extConfiguration) { + configurationCache = ConfigurationCache.getInstance().proxy(extConfiguration); + } else { + configurationCache = ConfigurationCache.getInstance().proxy(configuration); + } + if (null != configurationCache) { + extConfiguration = configurationCache; + } + } catch (EnhancedServiceNotFoundException ignore) { + + } catch (Exception e) { + LOGGER.error("failed to load configurationCacheProvider:{}", e.getMessage(), e); + } + return null == extConfiguration ? configuration : extConfiguration; + } + + protected static void reload() { + ConfigurationCache.getInstance().clear(); + load(); + instance = null; + getInstance(); + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationKeys.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationKeys.java new file mode 100644 index 0000000..13a91c2 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationKeys.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * The type Configuration keys. + * + * @author slievrly + */ +public interface ConfigurationKeys { + /** + * The constant FILE_ROOT_REGISTRY. + */ + String FILE_ROOT_REGISTRY = "registry"; + /** + * The constant FILE_ROOT_CONFIG. + */ + String FILE_ROOT_CONFIG = "config"; + /** + * The constant SEATA_FILE_ROOT_CONFIG + */ + String SEATA_FILE_ROOT_CONFIG = "seata"; + /** + * The constant FILE_CONFIG_SPLIT_CHAR. + */ + String FILE_CONFIG_SPLIT_CHAR = "."; + /** + * The constant FILE_ROOT_TYPE. + */ + String FILE_ROOT_TYPE = "type"; +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ConfigurationProvider.java b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationProvider.java new file mode 100644 index 0000000..c868323 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ConfigurationProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * the interface configuration provider + * @author xingfudeshi@gmail.com + */ +public interface ConfigurationProvider { + /** + * provide a AbstractConfiguration implementation instance + * @return Configuration + */ + Configuration provide(); +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/ExtConfigurationProvider.java b/config/seata-config-core/src/main/java/io/seata/config/ExtConfigurationProvider.java new file mode 100644 index 0000000..cc94b67 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/ExtConfigurationProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * the interface ext configuration provider + * @author xingfudeshi@gmail.com + */ +public interface ExtConfigurationProvider { + /** + * provide a AbstractConfiguration implementation instance + * @param originalConfiguration + * @return configuration + */ + Configuration provide(Configuration originalConfiguration); +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/FileConfigFactory.java b/config/seata-config-core/src/main/java/io/seata/config/FileConfigFactory.java new file mode 100644 index 0000000..271dda1 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/FileConfigFactory.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.config.file.FileConfig; +import java.io.File; +import java.util.LinkedHashMap; +import java.util.Set; + +/** + * @author wangwei-ying + */ +public class FileConfigFactory { + + public static final String DEFAULT_TYPE = "CONF"; + + public static final String YAML_TYPE = "YAML"; + + private static final LinkedHashMap SUFFIX_MAP = new LinkedHashMap(4) { + { + put("conf", DEFAULT_TYPE); + put("properties", DEFAULT_TYPE); + put("yml", YAML_TYPE); + } + }; + + + public static FileConfig load() { + return loadService(DEFAULT_TYPE, null, null); + } + + public static FileConfig load(File targetFile, String name) { + String fileName = targetFile.getName(); + String configType = getConfigType(fileName); + return loadService(configType, new Class[]{File.class, String.class}, new Object[]{targetFile, name}); + } + + private static String getConfigType(String fileName) { + String configType = DEFAULT_TYPE; + int suffixIndex = fileName.lastIndexOf("."); + if (suffixIndex > 0) { + configType = SUFFIX_MAP.getOrDefault(fileName.substring(suffixIndex + 1), DEFAULT_TYPE); + } + + return configType; + } + + private static FileConfig loadService(String name, Class[] argsType, Object[] args) { + FileConfig fileConfig = EnhancedServiceLoader.load(FileConfig.class, name, argsType, args); + return fileConfig; + } + + public static Set getSuffixSet() { + return SUFFIX_MAP.keySet(); + } + + public synchronized static void register(String suffix, String beanActiveName) { + SUFFIX_MAP.put(suffix, beanActiveName); + } + + +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/FileConfiguration.java b/config/seata-config-core/src/main/java/io/seata/config/FileConfiguration.java new file mode 100644 index 0000000..d129a1f --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/FileConfiguration.java @@ -0,0 +1,429 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.netty.util.internal.ConcurrentSet; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigFuture.ConfigOperation; +import io.seata.config.file.FileConfig; +import org.apache.commons.lang.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type FileConfiguration. + * + * @author slievrly + */ +public class FileConfiguration extends AbstractConfiguration { + + private static final Logger LOGGER = LoggerFactory.getLogger(FileConfiguration.class); + + private FileConfig fileConfig; + + private ExecutorService configOperateExecutor; + + private static final int CORE_CONFIG_OPERATE_THREAD = 1; + + private static final int MAX_CONFIG_OPERATE_THREAD = 2; + + private static final long LISTENER_CONFIG_INTERVAL = 1 * 1000; + + private static final String REGISTRY_TYPE = "file"; + + public static final String SYS_FILE_RESOURCE_PREFIX = "file:"; + + private final ConcurrentMap> configListenersMap = new ConcurrentHashMap<>( + 8); + + private final Map listenedConfigMap = new HashMap<>(8); + + private final String targetFilePath; + + private volatile long targetFileLastModified; + + private final String name; + + private final FileListener fileListener = new FileListener(); + + private final boolean allowDynamicRefresh; + + /** + * Note that:this constructor is only used to create proxy with CGLIB + * see io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#provide + */ + public FileConfiguration() { + this.name = null; + this.targetFilePath = null; + this.allowDynamicRefresh = false; + } + + /** + * Instantiates a new File configuration. + * + * @param name the name + */ + public FileConfiguration(String name) { + this(name, true); + } + + /** + * Instantiates a new File configuration. + * + * @param name the name + * @param allowDynamicRefresh the allow dynamic refresh + */ + public FileConfiguration(String name, boolean allowDynamicRefresh) { + LOGGER.info("The file name of the operation is {}", name); + File file = getConfigFile(name); + if (file == null) { + targetFilePath = null; + } else { + targetFilePath = file.getPath(); + fileConfig = FileConfigFactory.load(file, name); + } + /* + * For seata-server side the conf file should always exists. + * For application(or client) side,conf file may not exists when using seata-spring-boot-starter + */ + if (targetFilePath == null) { + fileConfig = FileConfigFactory.load(); + this.allowDynamicRefresh = false; + } else { + targetFileLastModified = new File(targetFilePath).lastModified(); + this.allowDynamicRefresh = allowDynamicRefresh; + } + + this.name = name; + configOperateExecutor = new ThreadPoolExecutor(CORE_CONFIG_OPERATE_THREAD, MAX_CONFIG_OPERATE_THREAD, + Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("configOperate", MAX_CONFIG_OPERATE_THREAD)); + } + + private File getConfigFile(String name) { + try { + if (name == null) { + throw new IllegalArgumentException("name can't be null"); + } + + boolean filePathCustom = name.startsWith(SYS_FILE_RESOURCE_PREFIX); + String filePath = filePathCustom ? name.substring(SYS_FILE_RESOURCE_PREFIX.length()) : name; + String decodedPath = URLDecoder.decode(filePath, StandardCharsets.UTF_8.name()); + + File targetFile = getFileFromFileSystem(decodedPath); + if (targetFile != null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("The configuration file used is {}", targetFile.getPath()); + } + return targetFile; + } + + if (!filePathCustom) { + File classpathFile = getFileFromClasspath(name); + if (classpathFile != null) { + return classpathFile; + } + } + } catch (UnsupportedEncodingException e) { + LOGGER.error("decode name error: {}", e.getMessage(), e); + } + + return null; + } + + private File getFileFromFileSystem(String decodedPath) { + + // run with jar file and not package third lib into jar file, this.getClass().getClassLoader() will be null + URL resourceUrl = this.getClass().getClassLoader().getResource(""); + String[] tryPaths = null; + if (resourceUrl != null) { + tryPaths = new String[]{ + // first: project dir + resourceUrl.getPath() + decodedPath, + // second: system path + decodedPath + }; + } else { + tryPaths = new String[]{ + decodedPath + }; + } + + + for (String tryPath : tryPaths) { + File targetFile = new File(tryPath); + if (targetFile.exists()) { + return targetFile; + } + + // try to append config suffix + for (String s : FileConfigFactory.getSuffixSet()) { + targetFile = new File(tryPath + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + s); + if (targetFile.exists()) { + return targetFile; + } + } + } + + return null; + } + + private File getFileFromClasspath(String name) throws UnsupportedEncodingException { + URL resource = this.getClass().getClassLoader().getResource(name); + if (resource == null) { + for (String s : FileConfigFactory.getSuffixSet()) { + resource = this.getClass().getClassLoader().getResource(name + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + s); + if (resource != null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("The configuration file used is {}", resource.getPath()); + } + String path = resource.getPath(); + path = URLDecoder.decode(path, StandardCharsets.UTF_8.name()); + return new File(path); + } + } + } else { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("The configuration file used is {}", name); + } + String path = resource.getPath(); + path = URLDecoder.decode(path, StandardCharsets.UTF_8.name()); + return new File(path); + } + + return null; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigOperation.GET, timeoutMills); + configOperateExecutor.submit(new ConfigOperateRunnable(configFuture)); + Object getValue = configFuture.get(); + return getValue == null ? null : String.valueOf(getValue); + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigOperation.PUT, timeoutMills); + configOperateExecutor.submit(new ConfigOperateRunnable(configFuture)); + return (Boolean) configFuture.get(); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigOperation.PUTIFABSENT, timeoutMills); + configOperateExecutor.submit(new ConfigOperateRunnable(configFuture)); + return (Boolean) configFuture.get(); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigOperation.REMOVE, timeoutMills); + configOperateExecutor.submit(new ConfigOperateRunnable(configFuture)); + return (Boolean) configFuture.get(); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>()) + .add(listener); + listenedConfigMap.put(dataId, ConfigurationFactory.getInstance().getConfig(dataId)); + + // Start config change listener for the dataId. + fileListener.addListener(dataId, listener); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + configListeners.remove(listener); + if (configListeners.isEmpty()) { + configListenersMap.remove(dataId); + listenedConfigMap.remove(dataId); + } + } + listener.onShutDown(); + } + + @Override + public Set getConfigListeners(String dataId) { + return configListenersMap.get(dataId); + } + + @Override + public String getTypeName() { + return REGISTRY_TYPE; + } + + /** + * The type Config operate runnable. + */ + class ConfigOperateRunnable implements Runnable { + + private ConfigFuture configFuture; + + /** + * Instantiates a new Config operate runnable. + * + * @param configFuture the config future + */ + public ConfigOperateRunnable(ConfigFuture configFuture) { + this.configFuture = configFuture; + } + + @Override + public void run() { + if (configFuture != null) { + if (configFuture.isTimeout()) { + setFailResult(configFuture); + return; + } + try { + if (allowDynamicRefresh) { + long tempLastModified = new File(targetFilePath).lastModified(); + if (tempLastModified > targetFileLastModified) { + FileConfig tempConfig = FileConfigFactory.load(new File(targetFilePath), name); + if (tempConfig != null) { + fileConfig = tempConfig; + targetFileLastModified = tempLastModified; + } + } + } + if (configFuture.getOperation() == ConfigOperation.GET) { + String result = fileConfig.getString(configFuture.getDataId()); + configFuture.setResult(result); + } else if (configFuture.getOperation() == ConfigOperation.PUT) { + //todo + configFuture.setResult(Boolean.TRUE); + } else if (configFuture.getOperation() == ConfigOperation.PUTIFABSENT) { + //todo + configFuture.setResult(Boolean.TRUE); + } else if (configFuture.getOperation() == ConfigOperation.REMOVE) { + //todo + configFuture.setResult(Boolean.TRUE); + } + } catch (Exception e) { + setFailResult(configFuture); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Could not found property {}, try to use default value instead. exception:{}", + configFuture.getDataId(), e.getMessage()); + } + } + } + } + + private void setFailResult(ConfigFuture configFuture) { + if (configFuture.getOperation() == ConfigOperation.GET) { + String result = configFuture.getContent(); + configFuture.setResult(result); + } else { + configFuture.setResult(Boolean.FALSE); + } + } + + } + + /** + * The type FileListener. + */ + class FileListener implements ConfigurationChangeListener { + + private final Map> dataIdMap = new HashMap<>(); + + private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("fileListener", MAX_LISTENER_THREAD)); + + /** + * Instantiates a new FileListener. + */ + FileListener() {} + + public synchronized void addListener(String dataId, ConfigurationChangeListener listener) { + // only the first time add listener will trigger on process event + if (dataIdMap.isEmpty()) { + fileListener.onProcessEvent(new ConfigurationChangeEvent()); + } + + dataIdMap.computeIfAbsent(dataId, value -> new HashSet<>()).add(listener); + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + while (true) { + for (String dataId : dataIdMap.keySet()) { + try { + String currentConfig = + ConfigurationFactory.getInstance().getLatestConfig(dataId, null, DEFAULT_CONFIG_TIMEOUT); + if (StringUtils.isNotBlank(currentConfig)) { + String oldConfig = listenedConfigMap.get(dataId); + if (ObjectUtils.notEqual(currentConfig, oldConfig)) { + listenedConfigMap.put(dataId, currentConfig); + event.setDataId(dataId).setNewValue(currentConfig).setOldValue(oldConfig); + + for (ConfigurationChangeListener listener : dataIdMap.get(dataId)) { + listener.onChangeEvent(event); + } + } + } + } catch (Exception exx) { + LOGGER.error("fileListener execute error, dataId :{}", dataId, exx); + } + } + try { + Thread.sleep(LISTENER_CONFIG_INTERVAL); + } catch (InterruptedException e) { + LOGGER.error("fileListener thread sleep error:{}", e.getMessage()); + } + } + } + + @Override + public ExecutorService getExecutorService() { + return executor; + } + } + +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/file/FileConfig.java b/config/seata-config-core/src/main/java/io/seata/config/file/FileConfig.java new file mode 100644 index 0000000..770568b --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/file/FileConfig.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.file; + + +/** + * @author wangwei-ying + */ +public interface FileConfig { + /** + * @param path path expression + */ + String getString(String path); + + +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/file/SimpleFileConfig.java b/config/seata-config-core/src/main/java/io/seata/config/file/SimpleFileConfig.java new file mode 100644 index 0000000..5a642eb --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/file/SimpleFileConfig.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.file; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.config.FileConfigFactory; +import io.seata.config.FileConfiguration; + +import java.io.File; + +/** + * @author wangwei-ying + */ +@LoadLevel(name = FileConfigFactory.DEFAULT_TYPE,scope = Scope.PROTOTYPE) +public class SimpleFileConfig implements FileConfig { + + private Config fileConfig; + + public SimpleFileConfig() { + fileConfig = ConfigFactory.load(); + } + + public SimpleFileConfig(File file, String name) { + if (name.startsWith(FileConfiguration.SYS_FILE_RESOURCE_PREFIX)) { + Config appConfig = ConfigFactory.parseFileAnySyntax(file); + fileConfig = ConfigFactory.load(appConfig); + } else { + fileConfig = ConfigFactory.load(file.getName()); + } + } + + @Override + public String getString(String path) { + return fileConfig.getString(path); + } +} diff --git a/config/seata-config-core/src/main/java/io/seata/config/file/YamlFileConfig.java b/config/seata-config-core/src/main/java/io/seata/config/file/YamlFileConfig.java new file mode 100644 index 0000000..0634ad4 --- /dev/null +++ b/config/seata-config-core/src/main/java/io/seata/config/file/YamlFileConfig.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.file; + +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.config.FileConfigFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.util.Map; + +/** + * @author wangwei-ying + */ +@LoadLevel(name = FileConfigFactory.YAML_TYPE, order = 1, scope = Scope.PROTOTYPE) +public class YamlFileConfig implements FileConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(YamlFileConfig.class); + private Map configMap; + + public YamlFileConfig(File file, String name) { + Yaml yaml = new Yaml(); + try { + configMap = (Map) yaml.load(new FileInputStream(file)); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("file not found"); + } + } + + @Override + public String getString(String path) { + try { + Map config = configMap; + String[] dataId = path.split("\\."); + for (int i = 0; i < dataId.length - 1; i++) { + if (config.containsKey(dataId[i])) { + config = (Map) config.get(dataId[i]); + } else { + return null; + } + } + Object value = config.get(dataId[dataId.length - 1]); + return value == null ? null : String.valueOf(value); + } catch (Exception e) { + LOGGER.warn("get config data error" + path, e); + return null; + } + } +} diff --git a/config/seata-config-core/src/main/resources/META-INF/services/io.seata.config.file.FileConfig b/config/seata-config-core/src/main/resources/META-INF/services/io.seata.config.file.FileConfig new file mode 100644 index 0000000..911c3d9 --- /dev/null +++ b/config/seata-config-core/src/main/resources/META-INF/services/io.seata.config.file.FileConfig @@ -0,0 +1,2 @@ +io.seata.config.file.SimpleFileConfig +io.seata.config.file.YamlFileConfig diff --git a/config/seata-config-core/src/main/resources/file.conf b/config/seata-config-core/src/main/resources/file.conf new file mode 100644 index 0000000..060558d --- /dev/null +++ b/config/seata-config-core/src/main/resources/file.conf @@ -0,0 +1,8 @@ +service { + #transaction service group mapping + vgroupMapping.my_test_tx_group = "default" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8091" + #disable seata + disableGlobalTransaction = false +} \ No newline at end of file diff --git a/config/seata-config-core/src/main/resources/registry.conf b/config/seata-config-core/src/main/resources/registry.conf new file mode 100644 index 0000000..b371c46 --- /dev/null +++ b/config/seata-config-core/src/main/resources/registry.conf @@ -0,0 +1,76 @@ +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + application = "seata-server" + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3 + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + group = "SEATA_GROUP" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + namespace = "application" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file.conf" + } +} diff --git a/config/seata-config-core/src/test/java/io.seata.config/ConfigProperty.java b/config/seata-config-core/src/test/java/io.seata.config/ConfigProperty.java new file mode 100644 index 0000000..3638e02 --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/ConfigProperty.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +/** + * @author wangwei-ying + */ +public interface ConfigProperty { + final String ENV_PROPERTY_KEY = "seataEnv"; + final String SYSTEM_PROPERTY_SEATA_CONFIG_NAME = "seata.config.name"; + final String REGISTRY_CONF_DEFAULT = "registry"; + +} diff --git a/config/seata-config-core/src/test/java/io.seata.config/ConfigurationCacheTests.java b/config/seata-config-core/src/test/java/io.seata.config/ConfigurationCacheTests.java new file mode 100644 index 0000000..12e7d61 --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/ConfigurationCacheTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.config; + +import io.seata.common.util.DurationUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +/** + * @author jsbxyyx + */ +public class ConfigurationCacheTests { + + @Test + public void testChangeValue() throws Exception { + Configuration configuration = new FileConfiguration("registry"); + configuration = ConfigurationCache.getInstance().proxy(configuration); + configuration.getBoolean("aaa", false); + ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("aaa", "true")); + boolean aaa = configuration.getBoolean("aaa", false); + Assertions.assertTrue(aaa); + + configuration.getShort("bbb", (short) 0); + ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("bbb", "1")); + short bbb = configuration.getShort("bbb", (short) 0); + Assertions.assertEquals((short) 1, bbb); + + configuration.getDuration("ccc", Duration.ZERO); + ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("ccc", "1s")); + Duration ccc = configuration.getDuration("ccc", Duration.ZERO); + Assertions.assertEquals(ccc, DurationUtil.parse("1s")); + + configuration.getInt("ddd", 0); + ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("ddd", "1")); + int ddd = configuration.getInt("ddd", 0); + Assertions.assertEquals(1, ddd); + + configuration.getLong("eee", 0); + ConfigurationCache.getInstance().onChangeEvent(new ConfigurationChangeEvent("eee", "1")); + long eee = configuration.getLong("eee", 0); + Assertions.assertEquals((long) 1, eee); + } + +} diff --git a/config/seata-config-core/src/test/java/io.seata.config/FileConfigurationTest.java b/config/seata-config-core/src/test/java/io.seata.config/FileConfigurationTest.java new file mode 100644 index 0000000..09c9ffb --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/FileConfigurationTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author slievrly + */ +class FileConfigurationTest { + + + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void addConfigListener() throws InterruptedException { + Configuration fileConfig = ConfigurationFactory.getInstance(); + CountDownLatch countDownLatch = new CountDownLatch(1); + boolean value = fileConfig.getBoolean("service.disableGlobalTransaction"); + fileConfig.addConfigListener("service.disableGlobalTransaction", (event) -> { + Assertions.assertEquals(Boolean.parseBoolean(event.getNewValue()), !Boolean.parseBoolean(event.getOldValue())); + countDownLatch.countDown(); + }); + System.setProperty("service.disableGlobalTransaction", String.valueOf(!value)); + Assertions.assertTrue(countDownLatch.await(2000, TimeUnit.MILLISECONDS)); + } + +} diff --git a/config/seata-config-core/src/test/java/io.seata.config/ProConfigurationFactoryTest.java b/config/seata-config-core/src/test/java/io.seata.config/ProConfigurationFactoryTest.java new file mode 100644 index 0000000..ea5607d --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/ProConfigurationFactoryTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY; +import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME; +import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT; + +/** + * @author wangwei-ying + */ +class ProConfigurationFactoryTest { + + @Test + void getInstance() { + System.setProperty(ENV_PROPERTY_KEY, "test-pro"); + System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME, REGISTRY_CONF_DEFAULT); + ConfigurationFactory.reload(); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"), "file-test-pro.conf"); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testBlank"), ""); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testNull"), null); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testExist"), null); + Configuration instance = ConfigurationFactory.getInstance(); + Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"), "true"); + Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8092"); + + } + + @AfterAll + public static void afterAll() { + System.clearProperty(ENV_PROPERTY_KEY); + System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); + ConfigurationFactory.reload(); + } +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/java/io.seata.config/RegistryConfigurationFactoryTest.java b/config/seata-config-core/src/test/java/io.seata.config/RegistryConfigurationFactoryTest.java new file mode 100644 index 0000000..4052b54 --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/RegistryConfigurationFactoryTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY; +import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME; +import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT; + + +/** + * @author wangwei-ying + */ +class RegistryConfigurationFactoryTest { + + @Test + void getInstance() { + System.setProperty(ENV_PROPERTY_KEY,"test"); + System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME,REGISTRY_CONF_DEFAULT); + ConfigurationFactory.reload(); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"),"file-test.conf"); + Configuration instance = ConfigurationFactory.getInstance(); + Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"),"true"); + Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8091"); + + } + @AfterAll + public static void afterAll(){ + System.clearProperty(ENV_PROPERTY_KEY); + System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); + ConfigurationFactory.reload(); + } +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/java/io.seata.config/YamlConfigurationFactoryTest.java b/config/seata-config-core/src/test/java/io.seata.config/YamlConfigurationFactoryTest.java new file mode 100644 index 0000000..d5d4ff2 --- /dev/null +++ b/config/seata-config-core/src/test/java/io.seata.config/YamlConfigurationFactoryTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static io.seata.config.ConfigProperty.ENV_PROPERTY_KEY; +import static io.seata.config.ConfigProperty.SYSTEM_PROPERTY_SEATA_CONFIG_NAME; +import static io.seata.config.ConfigProperty.REGISTRY_CONF_DEFAULT; + +/** + * @author wangwei-ying + */ +class YamlConfigurationFactoryTest { + + @Test + public void getInstance() { + System.setProperty(ENV_PROPERTY_KEY, "test-yaml"); + System.setProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME, REGISTRY_CONF_DEFAULT); + ConfigurationFactory.reload(); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.name"), "file-test-yaml.conf"); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testBlank"), ""); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testNull"), null); + Assertions.assertEquals(ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig("config.file.testExist"), null); + Configuration instance = ConfigurationFactory.getInstance(); + Assertions.assertEquals(instance.getConfig("service.disableGlobalTransaction"), "true"); + Assertions.assertEquals(instance.getConfig("service.default.grouplist"), "127.0.0.1:8093"); + } + + @AfterAll + public static void afterAll() { + System.clearProperty(ENV_PROPERTY_KEY); + System.clearProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); + ConfigurationFactory.reload(); + } +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/resources/file-test-pro.conf b/config/seata-config-core/src/test/resources/file-test-pro.conf new file mode 100644 index 0000000..c23cae3 --- /dev/null +++ b/config/seata-config-core/src/test/resources/file-test-pro.conf @@ -0,0 +1,8 @@ +service { + #transaction service group mapping + vgroupMapping.my_test_tx_group = "default" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8092" + #disable seata + disableGlobalTransaction = true +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/resources/file-test-yaml.conf b/config/seata-config-core/src/test/resources/file-test-yaml.conf new file mode 100644 index 0000000..196f350 --- /dev/null +++ b/config/seata-config-core/src/test/resources/file-test-yaml.conf @@ -0,0 +1,8 @@ +service { + #transaction service group mapping + vgroupMapping.my_test_tx_group = "default" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8093" + #disable seata + disableGlobalTransaction = true +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/resources/file-test.conf b/config/seata-config-core/src/test/resources/file-test.conf new file mode 100644 index 0000000..6599bd3 --- /dev/null +++ b/config/seata-config-core/src/test/resources/file-test.conf @@ -0,0 +1,8 @@ +service { + #transaction service group mapping + vgroupMapping.my_test_tx_group = "default" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8091" + #disable seata + disableGlobalTransaction = true +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/resources/file.conf b/config/seata-config-core/src/test/resources/file.conf new file mode 100644 index 0000000..060558d --- /dev/null +++ b/config/seata-config-core/src/test/resources/file.conf @@ -0,0 +1,8 @@ +service { + #transaction service group mapping + vgroupMapping.my_test_tx_group = "default" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8091" + #disable seata + disableGlobalTransaction = false +} \ No newline at end of file diff --git a/config/seata-config-core/src/test/resources/registry-test-pro.properties b/config/seata-config-core/src/test/resources/registry-test-pro.properties new file mode 100644 index 0000000..337cb27 --- /dev/null +++ b/config/seata-config-core/src/test/resources/registry-test-pro.properties @@ -0,0 +1,21 @@ + +#Copyright 1999-2019 Seata.io Group. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +#http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. +registry.type=file +registry.file.name=file-test-pro.conf + +config.type=file +config.file.name=file-test-pro.conf +config.file.testBlank= + diff --git a/config/seata-config-core/src/test/resources/registry-test-yaml.yml b/config/seata-config-core/src/test/resources/registry-test-yaml.yml new file mode 100644 index 0000000..df74932 --- /dev/null +++ b/config/seata-config-core/src/test/resources/registry-test-yaml.yml @@ -0,0 +1,11 @@ +registry: + type: file + file: + name: file.conf +config: + type: file + file: + name: file-test-yaml.conf + # for test + testBlank: '' + testNull: diff --git a/config/seata-config-core/src/test/resources/registry-test.conf b/config/seata-config-core/src/test/resources/registry-test.conf new file mode 100644 index 0000000..efe9f48 --- /dev/null +++ b/config/seata-config-core/src/test/resources/registry-test.conf @@ -0,0 +1,73 @@ +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file-test.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3 + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file-test.conf" + } +} diff --git a/config/seata-config-core/src/test/resources/registry.conf b/config/seata-config-core/src/test/resources/registry.conf new file mode 100644 index 0000000..0a9bfec --- /dev/null +++ b/config/seata-config-core/src/test/resources/registry.conf @@ -0,0 +1,73 @@ +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3 + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file.conf" + } +} diff --git a/config/seata-config-custom/pom.xml b/config/seata-config-custom/pom.xml new file mode 100644 index 0000000..a491cea --- /dev/null +++ b/config/seata-config-custom/pom.xml @@ -0,0 +1,35 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-custom + seata-config-custom ${project.version} + + + + io.seata + seata-config-core + ${project.parent.version} + + + diff --git a/config/seata-config-custom/src/main/java/io/seata/config/custom/CustomConfigurationProvider.java b/config/seata-config-custom/src/main/java/io/seata/config/custom/CustomConfigurationProvider.java new file mode 100644 index 0000000..1ddb499 --- /dev/null +++ b/config/seata-config-custom/src/main/java/io/seata/config/custom/CustomConfigurationProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.custom; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigType; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationKeys; +import io.seata.config.ConfigurationFactory; +import io.seata.config.ConfigurationProvider; + +import java.util.stream.Stream; + +/** + * @author ggndnn + */ +@LoadLevel(name = "Custom") +public class CustomConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + String pathDataId = ConfigurationKeys.FILE_ROOT_CONFIG + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + + ConfigType.Custom.name().toLowerCase() + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + + "name"; + String name = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(pathDataId); + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("name value of custom config type must not be blank"); + } + if (Stream.of(ConfigType.values()) + .anyMatch(ct -> ct.name().equalsIgnoreCase(name))) { + throw new IllegalArgumentException(String.format("custom config type name %s is not allowed", name)); + } + return EnhancedServiceLoader.load(ConfigurationProvider.class, name).provide(); + } +} diff --git a/config/seata-config-custom/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-custom/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..207829a --- /dev/null +++ b/config/seata-config-custom/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.custom.CustomConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationForTest.java b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationForTest.java new file mode 100644 index 0000000..577c195 --- /dev/null +++ b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationForTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; + +public class CustomConfigurationForTest extends AbstractConfiguration { + private Properties properties; + + public CustomConfigurationForTest(String name) { + try (InputStream input = CustomConfigurationForTest.class.getClassLoader().getResourceAsStream(name)) { + properties = new Properties(); + properties.load(input); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getTypeName() { + return "forTest"; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + return properties.getProperty(dataId, defaultValue); + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + throw new UnsupportedOperationException(); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getConfigListeners(String dataId) { + throw new UnsupportedOperationException(); + } +} diff --git a/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationProviderForTest.java b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationProviderForTest.java new file mode 100644 index 0000000..5b7d5f0 --- /dev/null +++ b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationProviderForTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import io.seata.common.loader.LoadLevel; + +/** + * @author ggndnn + */ +@LoadLevel(name = "forTest") +public class CustomConfigurationProviderForTest implements ConfigurationProvider { + @Override + public Configuration provide() { + return new CustomConfigurationForTest("custom_for_test.properties"); + } +} diff --git a/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationTest.java b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationTest.java new file mode 100644 index 0000000..854ea18 --- /dev/null +++ b/config/seata-config-custom/src/test/java/io/seata/config/CustomConfigurationTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import java.io.InputStream; +import java.util.Properties; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author ggndnn + */ +public class CustomConfigurationTest { + @Test + public void testCustomConfigLoad() throws Exception { + Configuration configuration = ConfigurationFactory.getInstance(); + Assertions.assertTrue(null != configuration); + Properties properties; + try (InputStream input = CustomConfigurationForTest.class.getClassLoader().getResourceAsStream("custom_for_test.properties")) { + properties = new Properties(); + properties.load(input); + } + Assertions.assertNotNull(properties); + for (String name : properties.stringPropertyNames()) { + String value = properties.getProperty(name); + Assertions.assertNotNull(value); + Assertions.assertEquals(value, configuration.getConfig(name)); + } + } +} \ No newline at end of file diff --git a/config/seata-config-custom/src/test/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-custom/src/test/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..e48a98d --- /dev/null +++ b/config/seata-config-custom/src/test/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.CustomConfigurationProviderForTest \ No newline at end of file diff --git a/config/seata-config-custom/src/test/resources/custom_for_test.properties b/config/seata-config-custom/src/test/resources/custom_for_test.properties new file mode 100644 index 0000000..5034a77 --- /dev/null +++ b/config/seata-config-custom/src/test/resources/custom_for_test.properties @@ -0,0 +1,20 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +transport.type=TCP +transport.server=NIO +service.default.grouplist=127.0.0.1:8091 +client.lock.retry.internal=10 \ No newline at end of file diff --git a/config/seata-config-custom/src/test/resources/registry.conf b/config/seata-config-custom/src/test/resources/registry.conf new file mode 100644 index 0000000..8e39de9 --- /dev/null +++ b/config/seata-config-custom/src/test/resources/registry.conf @@ -0,0 +1,7 @@ +config { + type = "custom" + + custom { + name = "forTest" + } +} \ No newline at end of file diff --git a/config/seata-config-etcd3/pom.xml b/config/seata-config-etcd3/pom.xml new file mode 100644 index 0000000..beb590d --- /dev/null +++ b/config/seata-config-etcd3/pom.xml @@ -0,0 +1,49 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-etcd3 + seata-config-etcd3 ${project.version} + + + + io.seata + seata-config-core + ${project.parent.version} + + + io.etcd + jetcd-core + + + com.google.guava + guava + + + + + com.google.guava + guava + + + diff --git a/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfiguration.java b/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfiguration.java new file mode 100644 index 0000000..5d91377 --- /dev/null +++ b/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfiguration.java @@ -0,0 +1,315 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.etcd3; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.KeyValue; +import io.etcd.jetcd.Watch; +import io.etcd.jetcd.kv.DeleteResponse; +import io.etcd.jetcd.kv.GetResponse; +import io.etcd.jetcd.kv.PutResponse; +import io.etcd.jetcd.kv.TxnResponse; +import io.etcd.jetcd.op.Cmp; +import io.etcd.jetcd.op.CmpTarget; +import io.etcd.jetcd.op.Op; +import io.etcd.jetcd.options.PutOption; +import io.etcd.jetcd.watch.WatchResponse; +import io.netty.util.internal.ConcurrentSet; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.ConfigFuture; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.netty.util.CharsetUtil.UTF_8; +import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; +import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG; + +/** + * The type Etcd configuration. + * + * @author xingfudeshi @gmail.com + */ +public class EtcdConfiguration extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(EtcdConfiguration.class); + private static volatile EtcdConfiguration instance; + private static volatile Client client; + + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String CONFIG_TYPE = "etcd3"; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE + + FILE_CONFIG_SPLIT_CHAR; + private static final int THREAD_POOL_NUM = 1; + private static final int MAP_INITIAL_CAPACITY = 8; + private ExecutorService etcdConfigExecutor; + private ConcurrentMap> configListenersMap = new ConcurrentHashMap<>( + MAP_INITIAL_CAPACITY); + + private static final long VERSION_NOT_EXIST = 0; + + private EtcdConfiguration() { + etcdConfigExecutor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, Integer.MAX_VALUE, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("etcd-config-executor", THREAD_POOL_NUM)); + } + + /** + * get instance + * + * @return instance + */ + public static EtcdConfiguration getInstance() { + if (instance == null) { + synchronized (EtcdConfiguration.class) { + if (instance == null) { + instance = new EtcdConfiguration(); + } + } + } + return instance; + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + ConfigFuture configFuture = new ConfigFuture(dataId, defaultValue, ConfigFuture.ConfigOperation.GET, + timeoutMills); + etcdConfigExecutor.execute( + () -> complete(getClient().getKVClient().get(ByteSequence.from(dataId, UTF_8)), configFuture)); + return (String)configFuture.get(); + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUT, timeoutMills); + etcdConfigExecutor.execute(() -> complete( + getClient().getKVClient().put(ByteSequence.from(dataId, UTF_8), ByteSequence.from(content, UTF_8)), + configFuture)); + return (Boolean)configFuture.get(); + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, content, ConfigFuture.ConfigOperation.PUTIFABSENT, + timeoutMills); + etcdConfigExecutor.execute(() -> { + //use etcd transaction to ensure the atomic operation + complete(client.getKVClient().txn() + //whether the key exists + .If(new Cmp(ByteSequence.from(dataId, UTF_8), Cmp.Op.EQUAL, CmpTarget.version(VERSION_NOT_EXIST))) + //not exist,then will create + .Then(Op.put(ByteSequence.from(dataId, UTF_8), ByteSequence.from(content, UTF_8), PutOption.DEFAULT)) + .commit(), configFuture); + }); + return (Boolean)configFuture.get(); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + ConfigFuture configFuture = new ConfigFuture(dataId, null, ConfigFuture.ConfigOperation.REMOVE, timeoutMills); + etcdConfigExecutor.execute(() -> complete(getClient().getKVClient().delete(ByteSequence.from(dataId, UTF_8)), configFuture)); + return (Boolean)configFuture.get(); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + EtcdListener etcdListener = new EtcdListener(dataId, listener); + configListenersMap.computeIfAbsent(dataId, key -> new ConcurrentSet<>()) + .add(etcdListener); + etcdListener.onProcessEvent(new ConfigurationChangeEvent()); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + ConfigurationChangeListener target; + for (ConfigurationChangeListener entry : configListeners) { + target = ((EtcdListener)entry).getTargetListener(); + if (listener.equals(target)) { + entry.onShutDown(); + configListeners.remove(entry); + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + return configListenersMap.get(dataId); + } + + /** + * get client + * + * @return client + */ + private static Client getClient() { + if (client == null) { + synchronized (EtcdConfiguration.class) { + if (client == null) { + client = Client.builder().endpoints(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY)) + .build(); + } + } + } + return client; + } + + /** + * complete the future + * + * @param completableFuture + * @param configFuture + * @param + */ + private void complete(CompletableFuture completableFuture, ConfigFuture configFuture) { + try { + T response = completableFuture.get(); + if (response instanceof GetResponse) { + List keyValues = ((GetResponse)response).getKvs(); + if (CollectionUtils.isNotEmpty(keyValues)) { + ByteSequence value = keyValues.get(0).getValue(); + if (value != null) { + configFuture.setResult(value.toString(UTF_8)); + } + } + } else if (response instanceof PutResponse) { + configFuture.setResult(Boolean.TRUE); + } else if (response instanceof TxnResponse) { + boolean result = ((TxnResponse)response).isSucceeded(); + //create key if file does not exist) + if (result) { + configFuture.setResult(Boolean.TRUE); + } + } else if (response instanceof DeleteResponse) { + configFuture.setResult(Boolean.TRUE); + } else { + throw new ShouldNeverHappenException("unsupported response type"); + } + } catch (Exception e) { + LOGGER.error("error occurred while completing the future{}", e.getMessage(),e); + } + } + + /** + * the type config change notifier + */ + private static class EtcdListener implements ConfigurationChangeListener { + private final String dataId; + private final ConfigurationChangeListener listener; + private Watch.Watcher watcher; + private final ExecutorService executor = new ThreadPoolExecutor(CORE_LISTENER_THREAD, MAX_LISTENER_THREAD, 0L, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("etcdListener", MAX_LISTENER_THREAD)); + + /** + * Instantiates a new Etcd listener. + * + * @param dataId the data id + * @param listener the listener + */ + public EtcdListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + } + + /** + * get the listener + * + * @return ConfigurationChangeListener target listener + */ + public ConfigurationChangeListener getTargetListener() { + return this.listener; + } + + @Override + public void onShutDown() { + this.watcher.close(); + getExecutorService().shutdownNow(); + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + Watch watchClient = getClient().getWatchClient(); + watcher = watchClient.watch(ByteSequence.from(dataId, UTF_8), new Watch.Listener() { + + @Override + public void onNext(WatchResponse watchResponse) { + try { + GetResponse getResponse = getClient().getKVClient().get(ByteSequence.from(dataId, UTF_8)).get(); + List keyValues = getResponse.getKvs(); + if (CollectionUtils.isNotEmpty(keyValues)) { + event.setDataId(dataId).setNewValue(keyValues.get(0).getValue().toString(UTF_8)); + listener.onChangeEvent(event); + } + } catch (Exception e) { + LOGGER.error("error occurred while getting value{}", e.getMessage(), e); + } + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onCompleted() { + + } + }); + } + + @Override + public ExecutorService getExecutorService() { + return executor; + } + } +} diff --git a/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfigurationProvider.java b/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfigurationProvider.java new file mode 100644 index 0000000..0bf9094 --- /dev/null +++ b/config/seata-config-etcd3/src/main/java/io/seata/config/etcd3/EtcdConfigurationProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.etcd3; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Etcd3", order = 1) +public class EtcdConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + return EtcdConfiguration.getInstance(); + } +} diff --git a/config/seata-config-etcd3/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-etcd3/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..22d6727 --- /dev/null +++ b/config/seata-config-etcd3/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.etcd3.EtcdConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-nacos/pom.xml b/config/seata-config-nacos/pom.xml new file mode 100644 index 0000000..943bb52 --- /dev/null +++ b/config/seata-config-nacos/pom.xml @@ -0,0 +1,40 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-nacos + seata-config-nacos ${project.version} + + + + io.seata + seata-config-core + ${project.parent.version} + + + com.alibaba.nacos + nacos-client + + + + diff --git a/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfiguration.java b/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfiguration.java new file mode 100644 index 0000000..c544810 --- /dev/null +++ b/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfiguration.java @@ -0,0 +1,388 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.nacos; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.config.listener.AbstractSharedListener; +import com.alibaba.nacos.api.exception.NacosException; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; +import io.seata.config.ConfigurationKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The type Nacos configuration. + * + * @author slievrly + */ +public class NacosConfiguration extends AbstractConfiguration { + private static volatile NacosConfiguration instance; + + private static final Logger LOGGER = LoggerFactory.getLogger(NacosConfiguration.class); + private static final String DEFAULT_GROUP = "SEATA_GROUP"; + private static final String DEFAULT_DATA_ID = "seata.properties"; + private static final String GROUP_KEY = "group"; + private static final String PRO_SERVER_ADDR_KEY = "serverAddr"; + private static final String NACOS_DATA_ID_KEY = "dataId"; + private static final String ENDPOINT_KEY = "endpoint"; + private static final String CONFIG_TYPE = "nacos"; + private static final String DEFAULT_NAMESPACE = ""; + private static final String PRO_NAMESPACE_KEY = "namespace"; + private static final String USER_NAME = "username"; + private static final String PASSWORD = "password"; + private static final String ACCESS_KEY = "accessKey"; + private static final String SECRET_KEY = "secretKey"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static volatile ConfigService configService; + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + private static volatile Properties seataConfig = new Properties(); + + /** + * Get instance of NacosConfiguration + * + * @return instance + */ + public static NacosConfiguration getInstance() { + if (instance == null) { + synchronized (NacosConfiguration.class) { + if (instance == null) { + instance = new NacosConfiguration(); + } + } + } + return instance; + } + + /** + * Instantiates a new Nacos configuration. + */ + private NacosConfiguration() { + if (configService == null) { + try { + configService = NacosFactory.createConfigService(getConfigProperties()); + initSeataConfig(); + } catch (NacosException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + + value = seataConfig.getProperty(dataId); + + if (null == value) { + try { + value = configService.getConfig(dataId, getNacosGroup(), timeoutMills); + } catch (NacosException exx) { + LOGGER.error(exx.getErrMsg()); + } + } + + return value == null ? defaultValue : value; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + boolean result = false; + try { + if (!seataConfig.isEmpty()) { + seataConfig.setProperty(dataId, content); + result = configService.publishConfig(getNacosDataId(), getNacosGroup(), getSeataConfigStr()); + } else { + result = configService.publishConfig(dataId, getNacosGroup(), content); + } + } catch (NacosException exx) { + LOGGER.error(exx.getErrMsg()); + } + return result; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + boolean result = false; + try { + if (!seataConfig.isEmpty()) { + seataConfig.remove(dataId); + result = configService.publishConfig(getNacosDataId(), getNacosGroup(), getSeataConfigStr()); + } else { + result = configService.removeConfig(dataId, getNacosGroup()); + } + } catch (NacosException exx) { + LOGGER.error(exx.getErrMsg()); + } + return result; + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + try { + NacosListener nacosListener = new NacosListener(dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, nacosListener); + configService.addListener(dataId, getNacosGroup(), nacosListener); + } catch (Exception exx) { + LOGGER.error("add nacos listener error:{}", exx.getMessage(), exx); + } + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + NacosListener nacosListener = null; + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + nacosListener = configListeners.get(listener); + configListeners.remove(entry); + } + if (nacosListener != null) { + configService.removeListener(dataId, getNacosGroup(), nacosListener); + } + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + return configListeners.keySet(); + } else { + return null; + } + } + + private static Properties getConfigProperties() { + Properties properties = new Properties(); + if (System.getProperty(ENDPOINT_KEY) != null) { + properties.setProperty(ENDPOINT_KEY, System.getProperty(ENDPOINT_KEY)); + properties.put(ACCESS_KEY, Objects.toString(System.getProperty(ACCESS_KEY), "")); + properties.put(SECRET_KEY, Objects.toString(System.getProperty(SECRET_KEY), "")); + } else if (System.getProperty(PRO_SERVER_ADDR_KEY) != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, System.getProperty(PRO_SERVER_ADDR_KEY)); + } else { + String address = FILE_CONFIG.getConfig(getNacosAddrFileKey()); + if (address != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, address); + } + } + + if (System.getProperty(PRO_NAMESPACE_KEY) != null) { + properties.setProperty(PRO_NAMESPACE_KEY, System.getProperty(PRO_NAMESPACE_KEY)); + } else { + String namespace = FILE_CONFIG.getConfig(getNacosNameSpaceFileKey()); + if (namespace == null) { + namespace = DEFAULT_NAMESPACE; + } + properties.setProperty(PRO_NAMESPACE_KEY, namespace); + } + String userName = StringUtils.isNotBlank(System.getProperty(USER_NAME)) ? System.getProperty(USER_NAME) + : FILE_CONFIG.getConfig(getNacosUserName()); + if (StringUtils.isNotBlank(userName)) { + String password = StringUtils.isNotBlank(System.getProperty(PASSWORD)) ? System.getProperty(PASSWORD) + : FILE_CONFIG.getConfig(getNacosPassword()); + if (StringUtils.isNotBlank(password)) { + properties.setProperty(USER_NAME, userName); + properties.setProperty(PASSWORD, password); + } + } + return properties; + } + + private static String getNacosNameSpaceFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PRO_NAMESPACE_KEY); + } + + private static String getNacosAddrFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PRO_SERVER_ADDR_KEY); + } + + private static String getNacosGroupKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, GROUP_KEY); + } + + private static String getNacosDataIdKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, NACOS_DATA_ID_KEY); + } + + private static String getNacosUserName() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, + USER_NAME); + } + + private static String getNacosPassword() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, + PASSWORD); + } + + private static String getNacosGroup() { + return FILE_CONFIG.getConfig(getNacosGroupKey(), DEFAULT_GROUP); + } + + private static String getNacosDataId() { + return FILE_CONFIG.getConfig(getNacosDataIdKey(), DEFAULT_DATA_ID); + } + + private static String getSeataConfigStr() { + StringBuilder sb = new StringBuilder(); + + Enumeration enumeration = seataConfig.propertyNames(); + while (enumeration.hasMoreElements()) { + String key = (String) enumeration.nextElement(); + String property = seataConfig.getProperty(key); + sb.append(key).append("=").append(property).append("\n"); + } + + return sb.toString(); + } + + private static void initSeataConfig() { + try { + String nacosDataId = getNacosDataId(); + String config = configService.getConfig(nacosDataId, getNacosGroup(), DEFAULT_CONFIG_TIMEOUT); + if (StringUtils.isNotBlank(config)) { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(config.getBytes()), StandardCharsets.UTF_8)) { + seataConfig.load(reader); + } + NacosListener nacosListener = new NacosListener(nacosDataId, null); + configService.addListener(nacosDataId, getNacosGroup(), nacosListener); + } + } catch (NacosException | IOException e) { + LOGGER.error("init config properties error", e); + } + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + /** + * Non-blocking subscriptions prohibit adding subscriptions in the thread pool to prevent thread termination + */ + public static class NacosListener extends AbstractSharedListener { + private final String dataId; + private final ConfigurationChangeListener listener; + + /** + * Instantiates a new Nacos listener. + * + * @param dataId the data id + * @param listener the listener + */ + public NacosListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + } + + /** + * Gets target listener. + * + * @return the target listener + */ + public ConfigurationChangeListener getTargetListener() { + return this.listener; + } + + @Override + public void innerReceive(String dataId, String group, String configInfo) { + //The new configuration method to puts all configurations into a dateId + if (getNacosDataId().equals(dataId)) { + Properties seataConfigNew = new Properties(); + if (StringUtils.isNotBlank(configInfo)) { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(configInfo.getBytes()), StandardCharsets.UTF_8)) { + seataConfigNew.load(reader); + } catch (IOException e) { + LOGGER.error("load config properties error", e); + return; + } + } + + //Get all the monitored dataids and judge whether it has been modified + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent event = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setNamespace(group); + + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(event); + } + } + } + + seataConfig = seataConfigNew; + return; + } + + //Compatible with old writing + ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setNewValue(configInfo) + .setNamespace(group); + listener.onProcessEvent(event); + } + } +} diff --git a/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfigurationProvider.java b/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfigurationProvider.java new file mode 100644 index 0000000..63d3582 --- /dev/null +++ b/config/seata-config-nacos/src/main/java/io/seata/config/nacos/NacosConfigurationProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.nacos; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Nacos", order = 1) +public class NacosConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + return NacosConfiguration.getInstance(); + } +} diff --git a/config/seata-config-nacos/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-nacos/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..64e931f --- /dev/null +++ b/config/seata-config-nacos/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.nacos.NacosConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-spring-cloud/pom.xml b/config/seata-config-spring-cloud/pom.xml new file mode 100644 index 0000000..e902ec7 --- /dev/null +++ b/config/seata-config-spring-cloud/pom.xml @@ -0,0 +1,25 @@ + + + + seata-config + io.seata + ${revision} + + 4.0.0 + + seata-config-spring-cloud + seata-config-spring-cloud ${project.version} + + + io.seata + seata-config-core + ${project.parent.version} + + + org.springframework + spring-context + + + \ No newline at end of file diff --git a/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/EnableSeataSpringConfig.java b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/EnableSeataSpringConfig.java new file mode 100644 index 0000000..0120212 --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/EnableSeataSpringConfig.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.springcloud; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Import({SpringApplicationContextProviderRegistrar.class}) +public @interface EnableSeataSpringConfig { +} diff --git a/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProvider.java b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProvider.java new file mode 100644 index 0000000..b437499 --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.springcloud; + +import io.seata.common.holder.ObjectHolder; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import static io.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; + +/** + * @author xingfudeshi@gmail.com + * The type spring application context provider + */ +public class SpringApplicationContextProvider implements ApplicationContextAware, BeanFactoryPostProcessor { + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ObjectHolder.INSTANCE.setObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT, applicationContext); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } +} diff --git a/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProviderRegistrar.java b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProviderRegistrar.java new file mode 100644 index 0000000..cc50f0c --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringApplicationContextProviderRegistrar.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.springcloud; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +import static io.seata.common.Constants.BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER; + +/** + * @author xingfudeshi@gmail.com + * The type spring application context provider registrar + */ +public class SpringApplicationContextProviderRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER)) { + AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(SpringApplicationContextProvider.class).getBeanDefinition(); + registry.registerBeanDefinition(BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, beanDefinition); + } + } +} \ No newline at end of file diff --git a/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfiguration.java b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfiguration.java new file mode 100644 index 0000000..c715dc4 --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.springcloud; + +import java.util.Set; + +import io.seata.common.holder.ObjectHolder; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.ConfigurationChangeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; + +public class SpringCloudConfiguration extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(SpringCloudConfiguration.class); + private static final String CONFIG_TYPE = "SpringCloudConfig"; + private static volatile SpringCloudConfiguration instance; + private static final String PREFIX = "seata."; + + public static SpringCloudConfiguration getInstance() { + if (instance == null) { + synchronized (SpringCloudConfiguration.class) { + if (instance == null) { + instance = new SpringCloudConfiguration(); + } + } + } + return instance; + } + + private SpringCloudConfiguration() { + + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + ApplicationContext applicationContext = ObjectHolder.INSTANCE.getObject(ApplicationContext.class); + if (applicationContext == null || applicationContext.getEnvironment() == null) { + return defaultValue; + } + String conf = applicationContext.getEnvironment().getProperty(PREFIX + dataId); + return StringUtils.isNotBlank(conf) ? conf : defaultValue; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + return false; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + return false; + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + return false; + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + LOGGER.warn("dynamic listening is not supported spring cloud config"); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + } + + @Override + public Set getConfigListeners(String dataId) { + return null; + } +} diff --git a/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfigurationProvider.java b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfigurationProvider.java new file mode 100644 index 0000000..130a1f9 --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/java/io/seata/config/springcloud/SpringCloudConfigurationProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.springcloud; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +@LoadLevel(name = "SpringCloudConfig", order = 1) +public class SpringCloudConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + return SpringCloudConfiguration.getInstance(); + } +} diff --git a/config/seata-config-spring-cloud/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-spring-cloud/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..e557674 --- /dev/null +++ b/config/seata-config-spring-cloud/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.springcloud.SpringCloudConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-zk/pom.xml b/config/seata-config-zk/pom.xml new file mode 100644 index 0000000..d169403 --- /dev/null +++ b/config/seata-config-zk/pom.xml @@ -0,0 +1,40 @@ + + + + + io.seata + seata-config + ${revision} + + 4.0.0 + seata-config-zk + seata-config-zk ${project.version} + + + + io.seata + seata-config-core + ${project.parent.version} + + + com.101tec + zkclient + + + + diff --git a/config/seata-config-zk/src/main/java/io/seata/config/zk/DefaultZkSerializer.java b/config/seata-config-zk/src/main/java/io/seata/config/zk/DefaultZkSerializer.java new file mode 100644 index 0000000..ae509a0 --- /dev/null +++ b/config/seata-config-zk/src/main/java/io/seata/config/zk/DefaultZkSerializer.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.zk; + +import org.I0Itec.zkclient.exception.ZkMarshallingError; +import org.I0Itec.zkclient.serialize.ZkSerializer; + +import java.nio.charset.StandardCharsets; + +/** + * Default zk serializer. + *

+ * If the user is not configured in config.zk.serializer configuration item, then use default serializer. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class DefaultZkSerializer implements ZkSerializer { + + @Override + public byte[] serialize(Object data) throws ZkMarshallingError { + return String.valueOf(data).getBytes(StandardCharsets.UTF_8); + } + + @Override + public Object deserialize(byte[] bytes) throws ZkMarshallingError { + return new String(bytes, StandardCharsets.UTF_8); + } +} diff --git a/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfiguration.java b/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfiguration.java new file mode 100644 index 0000000..70d681a --- /dev/null +++ b/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfiguration.java @@ -0,0 +1,388 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.zk; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Constructor; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.FutureTask; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.config.AbstractConfiguration; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationChangeType; +import io.seata.config.ConfigurationFactory; +import org.I0Itec.zkclient.IZkDataListener; +import org.I0Itec.zkclient.ZkClient; +import org.I0Itec.zkclient.serialize.ZkSerializer; +import org.apache.zookeeper.CreateMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; +import static io.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG; +import static io.seata.config.ConfigurationKeys.SEATA_FILE_ROOT_CONFIG; + +/** + * The type Zookeeper configuration. + * + * @author crazier.huang + */ +public class ZookeeperConfiguration extends AbstractConfiguration { + private final static Logger LOGGER = LoggerFactory.getLogger(ZookeeperConfiguration.class); + + private static final String CONFIG_TYPE = "zk"; + private static final String ZK_PATH_SPLIT_CHAR = "/"; + private static final String ROOT_PATH = ZK_PATH_SPLIT_CHAR + SEATA_FILE_ROOT_CONFIG; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String SESSION_TIMEOUT_KEY = "sessionTimeout"; + private static final String CONNECT_TIMEOUT_KEY = "connectTimeout"; + private static final String AUTH_USERNAME = "username"; + private static final String AUTH_PASSWORD = "password"; + private static final String SERIALIZER_KEY = "serializer"; + private static final String CONFIG_PATH_KEY = "nodePath"; + private static final int THREAD_POOL_NUM = 1; + private static final int DEFAULT_SESSION_TIMEOUT = 6000; + private static final int DEFAULT_CONNECT_TIMEOUT = 2000; + private static final String DEFAULT_CONFIG_PATH = ROOT_PATH + "/seata.properties"; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + CONFIG_TYPE + + FILE_CONFIG_SPLIT_CHAR; + private static final ExecutorService CONFIG_EXECUTOR = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, + Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("ZKConfigThread", THREAD_POOL_NUM)); + private static volatile ZkClient zkClient; + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + private static volatile Properties seataConfig = new Properties(); + + /** + * Instantiates a new Zookeeper configuration. + */ + public ZookeeperConfiguration() { + if (zkClient == null) { + synchronized (ZookeeperConfiguration.class) { + if (zkClient == null) { + ZkSerializer zkSerializer = getZkSerializer(); + String serverAddr = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY); + int sessionTimeout = FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIMEOUT_KEY, DEFAULT_SESSION_TIMEOUT); + int connectTimeout = FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT); + zkClient = new ZkClient(serverAddr, sessionTimeout, connectTimeout, zkSerializer); + String username = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_USERNAME); + String password = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_PASSWORD); + if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) { + StringBuilder auth = new StringBuilder(username).append(":").append(password); + zkClient.addAuthInfo("digest", auth.toString().getBytes()); + } + } + } + if (!zkClient.exists(ROOT_PATH)) { + zkClient.createPersistent(ROOT_PATH, true); + } + initSeataConfig(); + } + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = getConfigFromSysPro(dataId); + if (value != null) { + return value; + } + + value = seataConfig.getProperty(dataId); + if (value != null) { + return value; + } + + FutureTask future = new FutureTask<>(() -> { + String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId; + if (!zkClient.exists(path)) { + LOGGER.warn("config {} is not existed, return defaultValue {} ", + dataId, defaultValue); + return defaultValue; + } + String value1 = zkClient.readData(path); + return StringUtils.isNullOrEmpty(value1) ? defaultValue : value1; + }); + CONFIG_EXECUTOR.execute(future); + try { + return future.get(timeoutMills, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.error("getConfig {} error or timeout, return defaultValue {}, exception:{} ", + dataId, defaultValue, e.getMessage()); + return defaultValue; + } + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + if (!seataConfig.isEmpty()) { + seataConfig.setProperty(dataId, content); + zkClient.writeData(getConfigPath(), getSeataConfigStr()); + return true; + } + + FutureTask future = new FutureTask<>(() -> { + String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId; + if (!zkClient.exists(path)) { + zkClient.create(path, content, CreateMode.PERSISTENT); + } else { + zkClient.writeData(path, content); + } + return true; + }); + CONFIG_EXECUTOR.execute(future); + try { + return future.get(timeoutMills, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.error("putConfig {}, value: {} is error or timeout, exception: {}", + dataId, content, e.getMessage()); + return false; + } + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + if (!seataConfig.isEmpty()) { + seataConfig.remove(dataId); + zkClient.writeData(getConfigPath(), getSeataConfigStr()); + return true; + } + + FutureTask future = new FutureTask<>(() -> { + String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId; + return zkClient.delete(path); + }); + CONFIG_EXECUTOR.execute(future); + try { + return future.get(timeoutMills, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.error("removeConfig {} is error or timeout, exception:{}", dataId, e.getMessage()); + return false; + } + + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + + if (!seataConfig.isEmpty()) { + ZKListener zkListener = new ZKListener(dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, zkListener); + return; + } + + String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId; + if (zkClient.exists(path)) { + ZKListener zkListener = new ZKListener(path, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, zkListener); + zkClient.subscribeDataChanges(path, zkListener); + } + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + String path = ROOT_PATH + ZK_PATH_SPLIT_CHAR + dataId; + if (zkClient.exists(path)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + ZKListener zkListener = null; + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + zkListener = configListeners.get(listener); + configListeners.remove(entry); + } + if (zkListener != null) { + zkClient.unsubscribeDataChanges(path, zkListener); + } + break; + } + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + return configListeners.keySet(); + } else { + return null; + } + } + + private void initSeataConfig() { + String configPath = getConfigPath(); + String config = zkClient.readData(configPath, true); + if (StringUtils.isNotBlank(config)) { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(config.getBytes()), StandardCharsets.UTF_8)) { + seataConfig.load(reader); + } catch (IOException e) { + LOGGER.error("init config properties error", e); + } + ZKListener zkListener = new ZKListener(configPath, null); + zkClient.subscribeDataChanges(configPath, zkListener); + } + } + + private static String getConfigPath() { + return FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + CONFIG_PATH_KEY, DEFAULT_CONFIG_PATH); + } + + private static String getSeataConfigStr() { + StringBuilder sb = new StringBuilder(); + + Enumeration enumeration = seataConfig.propertyNames(); + while (enumeration.hasMoreElements()) { + String key = (String) enumeration.nextElement(); + String property = seataConfig.getProperty(key); + sb.append(key).append("=").append(property).append("\n"); + } + + return sb.toString(); + } + + /** + * The type Zk listener. + */ + public static class ZKListener implements IZkDataListener { + + private String path; + private ConfigurationChangeListener listener; + + /** + * Instantiates a new Zk listener. + * + * @param path the path + * @param listener the listener + */ + public ZKListener(String path, ConfigurationChangeListener listener) { + this.path = path; + this.listener = listener; + } + + @Override + public void handleDataChange(String s, Object o) { + if (s.equals(getConfigPath())) { + Properties seataConfigNew = new Properties(); + if (StringUtils.isNotBlank(o.toString())) { + try (Reader reader = new InputStreamReader(new ByteArrayInputStream(o.toString().getBytes()), StandardCharsets.UTF_8)) { + seataConfigNew.load(reader); + } catch (IOException e) { + LOGGER.error("load config properties error", e); + return; + } + } + + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent event = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setChangeType(ConfigurationChangeType.MODIFY); + + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(event); + } + } + } + seataConfig = seataConfigNew; + + return; + } + String dataId = s.replaceFirst(ROOT_PATH + ZK_PATH_SPLIT_CHAR, ""); + ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setNewValue(o.toString()) + .setChangeType(ConfigurationChangeType.MODIFY); + listener.onProcessEvent(event); + } + + @Override + public void handleDataDeleted(String s) { + String dataId = s.replaceFirst(ROOT_PATH + ZK_PATH_SPLIT_CHAR, ""); + ConfigurationChangeEvent event = new ConfigurationChangeEvent().setDataId(dataId).setChangeType( + ConfigurationChangeType.DELETE); + listener.onProcessEvent(event); + } + } + + private ZkSerializer getZkSerializer() { + ZkSerializer zkSerializer = null; + String serializer = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERIALIZER_KEY); + if (StringUtils.isNotBlank(serializer)) { + try { + Class clazz = Class.forName(serializer); + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + zkSerializer = (ZkSerializer) constructor.newInstance(); + } catch (ClassNotFoundException cfe) { + LOGGER.warn("No zk serializer class found, serializer:{}", serializer, cfe); + } catch (Throwable cause) { + LOGGER.warn("found zk serializer encountered an unknown exception", cause); + } + } + if (zkSerializer == null) { + zkSerializer = new DefaultZkSerializer(); + LOGGER.info("Use default zk serializer: io.seata.config.zk.DefaultZkSerializer."); + } + return zkSerializer; + } + +} diff --git a/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfigurationProvider.java b/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfigurationProvider.java new file mode 100644 index 0000000..09506c5 --- /dev/null +++ b/config/seata-config-zk/src/main/java/io/seata/config/zk/ZookeeperConfigurationProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config.zk; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "ZK", order = 1) +public class ZookeeperConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + try { + return new ZookeeperConfiguration(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/config/seata-config-zk/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider b/config/seata-config-zk/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider new file mode 100644 index 0000000..dcb677f --- /dev/null +++ b/config/seata-config-zk/src/main/resources/META-INF/services/io.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +io.seata.config.zk.ZookeeperConfigurationProvider \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..9ab2dd3 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,71 @@ + + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-core + jar + seata-core ${project.version} + + + + ${project.groupId} + seata-common + ${project.version} + + + ${project.groupId} + seata-discovery-core + ${project.version} + + + io.netty + netty-all + + + commons-pool + commons-pool + + + org.apache.commons + commons-dbcp2 + test + + + com.google.guava + guava + + + com.h2database + h2 + test + + + com.alibaba + fastjson + test + + + + diff --git a/core/src/main/java/io/seata/core/README.md b/core/src/main/java/io/seata/core/README.md new file mode 100644 index 0000000..44f2f5d --- /dev/null +++ b/core/src/main/java/io/seata/core/README.md @@ -0,0 +1,80 @@ +## request + +rm client -> server + +``` +RegisterRMRequest + +MergedWarpMessage + +BranchRegisterRequest +BranchReportRequest +GlobalLockQueryRequest +``` + +tm client -> server + +``` +RegisterTMRequest + +MergedWarpMessage + +GlobalBeginRequest +GlobalCommitRequest +GlobalRollbackRequest +GlobalStatusRequest +GlobalReportRequest +``` + +server -> rm client + +``` +BranchCommitRequest +BranchRollbackRequest +UndoLogDeleteRequest +``` + +server -> tm client + +``` +// null +``` + +## response + +Server -> rm client + +``` +RegisterRMResponse + +MergeResultMessage +BranchRegisterResponse +BranchReportResponse +GlobalLockQueryResponse +``` + +Server -> tm client + +``` +RegisterTMResponse + +MergeResultMessage +GlobalBeginResponse +GlobalCommitResponse +GlobalReportResponse +GlobalRollbackResponse +``` + +rm client -> server + +``` +BranchCommitResponse +BranchRollbackResponse +``` + +tm client -> server + +``` +// null +``` + diff --git a/core/src/main/java/io/seata/core/auth/AuthSigner.java b/core/src/main/java/io/seata/core/auth/AuthSigner.java new file mode 100644 index 0000000..4be1010 --- /dev/null +++ b/core/src/main/java/io/seata/core/auth/AuthSigner.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.core.auth; + +/** + * @author slievrly + */ +public interface AuthSigner { + String sign(String data, String key); +} diff --git a/core/src/main/java/io/seata/core/auth/DefaultAuthSigner.java b/core/src/main/java/io/seata/core/auth/DefaultAuthSigner.java new file mode 100644 index 0000000..6965662 --- /dev/null +++ b/core/src/main/java/io/seata/core/auth/DefaultAuthSigner.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.core.auth; + +import io.seata.common.loader.LoadLevel; + +/** + * @author slievrly + */ +@LoadLevel(name = "defaultAuthSigner", order = 100) +public class DefaultAuthSigner implements AuthSigner { + @Override + public String sign(String data, String key) { + return data; + } +} diff --git a/core/src/main/java/io/seata/core/compressor/Compressor.java b/core/src/main/java/io/seata/core/compressor/Compressor.java new file mode 100644 index 0000000..8ad8d69 --- /dev/null +++ b/core/src/main/java/io/seata/core/compressor/Compressor.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.compressor; + +/** + * @author jsbxyyx + */ +public interface Compressor { + + /** + * compress byte[] to byte[]. + * @param bytes the bytes + * @return the byte[] + */ + byte[] compress(byte[] bytes); + + /** + * decompress byte[] to byte[]. + * @param bytes the bytes + * @return the byte[] + */ + byte[] decompress(byte[] bytes); + +} diff --git a/core/src/main/java/io/seata/core/compressor/CompressorFactory.java b/core/src/main/java/io/seata/core/compressor/CompressorFactory.java new file mode 100644 index 0000000..a4870bb --- /dev/null +++ b/core/src/main/java/io/seata/core/compressor/CompressorFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.compressor; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * the type compressor factory + * @author jsbxyyx + */ +public class CompressorFactory { + + /** + * The constant COMPRESSOR_MAP. + */ + protected static final Map COMPRESSOR_MAP = new ConcurrentHashMap<>(); + + static { + COMPRESSOR_MAP.put(CompressorType.NONE, new NoneCompressor()); + } + + /** + * Get compressor by code. + * + * @param code the code + * @return the compressor + */ + public static Compressor getCompressor(byte code) { + CompressorType type = CompressorType.getByCode(code); + return CollectionUtils.computeIfAbsent(COMPRESSOR_MAP, type, + key -> EnhancedServiceLoader.load(Compressor.class, type.name())); + } + + /** + * None compressor + */ + @LoadLevel(name = "NONE") + public static class NoneCompressor implements Compressor { + @Override + public byte[] compress(byte[] bytes) { + return bytes; + } + + @Override + public byte[] decompress(byte[] bytes) { + return bytes; + } + } + +} diff --git a/core/src/main/java/io/seata/core/compressor/CompressorType.java b/core/src/main/java/io/seata/core/compressor/CompressorType.java new file mode 100644 index 0000000..652ebc5 --- /dev/null +++ b/core/src/main/java/io/seata/core/compressor/CompressorType.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.compressor; + +/** + * @author Geng Zhang + */ +public enum CompressorType { + + /** + * Not compress + */ + NONE((byte) 0), + + /** + * The gzip. + */ + GZIP((byte) 1), + + /** + * The zip. + */ + ZIP((byte) 2), + + /** + * The sevenz. + */ + SEVENZ((byte) 3), + + /** + * The bzip2. + */ + BZIP2((byte) 4), + + /** + * The lz4. + */ + LZ4((byte) 5), + + /** + * The deflater. + */ + DEFLATER((byte) 6); + + private final byte code; + + CompressorType(final byte code) { + this.code = code; + } + + /** + * Gets result code. + * + * @param code the code + * @return the result code + */ + public static CompressorType getByCode(int code) { + for (CompressorType b : CompressorType.values()) { + if (code == b.code) { + return b; + } + } + throw new IllegalArgumentException("unknown codec:" + code); + } + + /** + * Gets result code. + * + * @param name the code + * @return the result code + */ + public static CompressorType getByName(String name) { + for (CompressorType b : CompressorType.values()) { + if (b.name().equalsIgnoreCase(name)) { + return b; + } + } + throw new IllegalArgumentException("unknown codec:" + name); + } + + /** + * Gets code. + * + * @return the code + */ + public byte getCode() { + return code; + } +} diff --git a/core/src/main/java/io/seata/core/constants/ClientTableColumnsName.java b/core/src/main/java/io/seata/core/constants/ClientTableColumnsName.java new file mode 100644 index 0000000..dc17736 --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/ClientTableColumnsName.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +/** + * client table columns name. + * + * @author zjinlei + */ +public interface ClientTableColumnsName { + + /** + * The constant undo_log column name xid + * this field is not use in mysql + */ + String UNDO_LOG_ID = "id"; + + /** + * The constant undo_log column name xid + */ + String UNDO_LOG_XID = "xid"; + + /** + * The constant undo_log column name branch_id + */ + String UNDO_LOG_BRANCH_XID = "branch_id"; + + /** + * The constant undo_log column name context + */ + String UNDO_LOG_CONTEXT = "context"; + + /** + * The constant undo_log column name rollback_info + */ + String UNDO_LOG_ROLLBACK_INFO = "rollback_info"; + + /** + * The constant undo_log column name log_status + */ + String UNDO_LOG_LOG_STATUS = "log_status"; + + /** + * The constant undo_log column name log_created + */ + String UNDO_LOG_LOG_CREATED = "log_created"; + + /** + * The constant undo_log column name log_modified + */ + String UNDO_LOG_LOG_MODIFIED = "log_modified"; +} diff --git a/core/src/main/java/io/seata/core/constants/ConfigurationKeys.java b/core/src/main/java/io/seata/core/constants/ConfigurationKeys.java new file mode 100644 index 0000000..a2aac4d --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/ConfigurationKeys.java @@ -0,0 +1,660 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +/** + * The type Configuration keys. + * + * @author slievrly + */ +public interface ConfigurationKeys { + + /** + * The constant SEATA_PREFIX. + */ + String SEATA_PREFIX = "seata."; + + /** + * The constant SERVICE_PREFIX. + */ + String SERVICE_PREFIX = "service."; + + /** + * The constant STORE_PREFIX. + */ + String STORE_PREFIX = "store."; + + /** + * The constant STORE_MODE. + */ + String STORE_MODE = STORE_PREFIX + "mode"; + + /** + * The constant STORE_PUBLIC_KEY. + */ + public static final String STORE_PUBLIC_KEY = STORE_PREFIX + "publicKey"; + + /** + * The constant STORE_FILE_PREFIX + */ + String STORE_FILE_PREFIX = STORE_PREFIX + "file."; + + /** + * The constant STORE_FILE_DIR + */ + String STORE_FILE_DIR = STORE_FILE_PREFIX + "dir"; + + /** + * The constant SERVICE_GROUP_MAPPING_PREFIX. + */ + String SERVICE_GROUP_MAPPING_PREFIX = SERVICE_PREFIX + "vgroupMapping."; + /** + * The constant GROUPLIST_POSTFIX. + */ + String GROUPLIST_POSTFIX = ".grouplist"; + /** + * The constant SERVER_NODE_SPLIT_CHAR. + */ + String SERVER_NODE_SPLIT_CHAR = System.getProperty("line.separator"); + + /** + * The constant ENABLE_DEGRADE_POSTFIX. + */ + String ENABLE_DEGRADE_POSTFIX = "enableDegrade"; + + /** + * The constant CLIENT_PREFIX. + */ + String CLIENT_PREFIX = "client."; + + /** + * The constant SERVER_PREFIX. + */ + String SERVER_PREFIX = "server."; + + /** + * The constant TRANSPORT_PREFIX. + */ + String TRANSPORT_PREFIX = "transport."; + + /** + * The constant CLIENT_RM_PREFIX. + */ + String CLIENT_RM_PREFIX = CLIENT_PREFIX + "rm."; + + /** + * The constant CLIENT_ASYNC_COMMIT_BUFFER_LIMIT. + */ + String CLIENT_ASYNC_COMMIT_BUFFER_LIMIT = CLIENT_RM_PREFIX + "asyncCommitBufferLimit"; + /** + * The constant CLIENT_RM_LOCK_PREFIX. + */ + String CLIENT_RM_LOCK_PREFIX = CLIENT_RM_PREFIX + "lock."; + + /** + * The constant CLIENT_LOCK_RETRY_TIMES. + */ + String CLIENT_LOCK_RETRY_TIMES = CLIENT_RM_LOCK_PREFIX + "retryTimes"; + /** + * The constant CLIENT_LOCK_RETRY_INTERVAL. + */ + String CLIENT_LOCK_RETRY_INTERVAL = CLIENT_RM_LOCK_PREFIX + "retryInterval"; + /** + * The constant CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT. + */ + String CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT = CLIENT_RM_LOCK_PREFIX + "retryPolicyBranchRollbackOnConflict"; + + + /** + * The constant SERVICE_SESSION_RELOAD_READ_SIZE + */ + String SERVICE_SESSION_RELOAD_READ_SIZE = STORE_FILE_PREFIX + "sessionReloadReadSize"; + + /** + * The constant CLIENT_REPORT_SUCCESS_ENABLE. + */ + String CLIENT_REPORT_SUCCESS_ENABLE = CLIENT_RM_PREFIX + "reportSuccessEnable"; + + /** + * The constant CLIENT_SAGA_BRANCH_REGISTER_ENABLE. + */ + String CLIENT_SAGA_BRANCH_REGISTER_ENABLE = CLIENT_RM_PREFIX + "sagaBranchRegisterEnable"; + + /** + * The constant CLIENT_SAGA_JSON_PARSER. + */ + String CLIENT_SAGA_JSON_PARSER = CLIENT_RM_PREFIX + "sagaJsonParser"; + + /** + * The constant CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE. + */ + String CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE = CLIENT_RM_PREFIX + "sagaRetryPersistModeUpdate"; + + /** + * The constant CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE. + */ + String CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE = CLIENT_RM_PREFIX + "sagaCompensatePersistModeUpdate"; + + /** + * The constant CLIENT_REPORT_RETRY_COUNT. + */ + String CLIENT_REPORT_RETRY_COUNT = CLIENT_RM_PREFIX + "reportRetryCount"; + + /** + * The constant CLIENT_TABLE_META_CHECK_ENABLE. + */ + String CLIENT_TABLE_META_CHECK_ENABLE = CLIENT_RM_PREFIX + "tableMetaCheckEnable"; + + /** + * The constant CLIENT_TABLE_META_CHECKER_INTERVAL. + */ + String CLIENT_TABLE_META_CHECKER_INTERVAL = CLIENT_RM_PREFIX + "tableMetaCheckerInterval"; + + /** + * The constant CLIENT_TM_PREFIX. + */ + String CLIENT_TM_PREFIX = CLIENT_PREFIX + "tm."; + /** + * The constant CLIENT_TM_COMMIT_RETRY_TIMES. + */ + String CLIENT_TM_COMMIT_RETRY_COUNT = CLIENT_TM_PREFIX + "commitRetryCount"; + + /** + * The constant CLIENT_TM_ROLLBACK_RETRY_TIMES. + */ + String CLIENT_TM_ROLLBACK_RETRY_COUNT = CLIENT_TM_PREFIX + "rollbackRetryCount"; + + /** + * The constant DEFAULT_GLOBAL_TRANSACTION_TIMEOUT. + */ + String DEFAULT_GLOBAL_TRANSACTION_TIMEOUT = CLIENT_TM_PREFIX + "defaultGlobalTransactionTimeout"; + + /** + * The constant SERIALIZE_FOR_RPC. + */ + String SERIALIZE_FOR_RPC = TRANSPORT_PREFIX + "serialization"; + + /** + * The constant COMPRESSOR_FOR_RPC. + * + * @since 0.7.0 + */ + String COMPRESSOR_FOR_RPC = TRANSPORT_PREFIX + "compressor"; + + /** + * The constant STORE_DB_PREFIX. + */ + String STORE_DB_PREFIX = "store.db."; + + /** + * The constant STORE_REDIS_PREFIX. + */ + String STORE_REDIS_PREFIX = "store.redis."; + + /** + * The constant STORE_DB_GLOBAL_TABLE. + */ + String STORE_DB_GLOBAL_TABLE = STORE_DB_PREFIX + "globalTable"; + + /** + * The constant STORE_DB_BRANCH_TABLE. + */ + String STORE_DB_BRANCH_TABLE = STORE_DB_PREFIX + "branchTable"; + + /** + * The constant STORE_DB_DATASOURCE_TYPE. + */ + String STORE_DB_DATASOURCE_TYPE = STORE_DB_PREFIX + "datasource"; + + /** + * The constant STORE_DB_TYPE. + */ + String STORE_DB_TYPE = STORE_DB_PREFIX + "dbType"; + + /** + * The constant STORE_DB_DRIVER_CLASS_NAME. + */ + String STORE_DB_DRIVER_CLASS_NAME = STORE_DB_PREFIX + "driverClassName"; + + /** + * The constant STORE_DB_MAX_WAIT. + */ + String STORE_DB_MAX_WAIT = STORE_DB_PREFIX + "maxWait"; + + /** + * The constant STORE_DB_URL. + */ + String STORE_DB_URL = STORE_DB_PREFIX + "url"; + + /** + * The constant STORE_DB_USER. + */ + String STORE_DB_USER = STORE_DB_PREFIX + "user"; + + /** + * The constant STORE_DB_PASSWORD. + */ + String STORE_DB_PASSWORD = STORE_DB_PREFIX + "password"; + + /** + * The constant STORE_DB_MIN_CONN. + */ + String STORE_DB_MIN_CONN = STORE_DB_PREFIX + "minConn"; + + /** + * The constant STORE_DB_MAX_CONN. + */ + String STORE_DB_MAX_CONN = STORE_DB_PREFIX + "maxConn"; + + /** + * The constant STORE_DB_LOG_QUERY_LIMIT. + */ + String STORE_DB_LOG_QUERY_LIMIT = STORE_DB_PREFIX + "queryLimit"; + + /** + * The constant LOCK_DB_TABLE. + */ + String LOCK_DB_TABLE = STORE_DB_PREFIX + "lockTable"; + + /** + * The constant SERVER_PORT. + */ + String SERVER_PORT = SERVER_PREFIX + "port"; + + /** + * The constant RECOVERY_PREFIX. + */ + String RECOVERY_PREFIX = SERVER_PREFIX + "recovery."; + /** + * The constant COMMITING_RETRY_PERIOD. + */ + String COMMITING_RETRY_PERIOD = RECOVERY_PREFIX + "committingRetryPeriod"; + + /** + * The constant ASYN_COMMITING_RETRY_PERIOD. + */ + String ASYN_COMMITING_RETRY_PERIOD = RECOVERY_PREFIX + "asynCommittingRetryPeriod"; + + /** + * The constant ROLLBACKING_RETRY_PERIOD. + */ + String ROLLBACKING_RETRY_PERIOD = RECOVERY_PREFIX + "rollbackingRetryPeriod"; + + /** + * The constant TIMEOUT_RETRY_PERIOD. + */ + String TIMEOUT_RETRY_PERIOD = RECOVERY_PREFIX + "timeoutRetryPeriod"; + + /** + * The constant CLIENT_UNDO_PREFIX. + */ + String CLIENT_UNDO_PREFIX = "client.undo."; + + /** + * The constant TRANSACTION_UNDO_DATA_VALIDATION. + */ + String TRANSACTION_UNDO_DATA_VALIDATION = CLIENT_UNDO_PREFIX + "dataValidation"; + /** + * The constant TRANSACTION_UNDO_LOG_SERIALIZATION. + */ + String TRANSACTION_UNDO_LOG_SERIALIZATION = CLIENT_UNDO_PREFIX + "logSerialization"; + + /** + * The constant TRANSACTION_UNDO_ONLY_CARE_UPDATE_COLUMNS. + */ + String TRANSACTION_UNDO_ONLY_CARE_UPDATE_COLUMNS = CLIENT_UNDO_PREFIX + "onlyCareUpdateColumns"; + + /** + * the constant CLIENT_UNDO_COMPRESS_PREFIX + */ + String CLIENT_UNDO_COMPRESS_PREFIX = CLIENT_UNDO_PREFIX + "compress."; + + /** + * the constant CLIENT_UNDO_COMPRESS_TYPE + */ + String CLIENT_UNDO_COMPRESS_TYPE = CLIENT_UNDO_COMPRESS_PREFIX + "type"; + + /** + * the constant CLIENT_UNDO_COMPRESS_ENABLE + */ + String CLIENT_UNDO_COMPRESS_ENABLE = CLIENT_UNDO_COMPRESS_PREFIX + "enable"; + + /** + * the constant CLIENT_UNDO_COMPRESS_THRESHOLD + */ + String CLIENT_UNDO_COMPRESS_THRESHOLD = CLIENT_UNDO_COMPRESS_PREFIX + "threshold"; + + /** + * The constant METRICS_PREFIX. + */ + String METRICS_PREFIX = "metrics."; + + /** + * The constant METRICS_ENABLED. + */ + String METRICS_ENABLED = "enabled"; + + /** + * The constant METRICS_REGISTRY_TYPE. + */ + String METRICS_REGISTRY_TYPE = "registryType"; + + /** + * The constant METRICS_EXPORTER_LIST. + */ + String METRICS_EXPORTER_LIST = "exporterList"; + /** + * The constant METRICS_EXPORTER_PROMETHEUS_PORT + */ + String METRICS_EXPORTER_PROMETHEUS_PORT = "exporterPrometheusPort"; + + /** + * The constant SERVER_UNDO_PREFIX. + */ + String SERVER_UNDO_PREFIX = SERVER_PREFIX + "undo."; + + /** + * The constant TRANSACTION_UNDO_LOG_SAVE_DAYS. + */ + String TRANSACTION_UNDO_LOG_SAVE_DAYS = SERVER_UNDO_PREFIX + "logSaveDays"; + + /** + * The constant TRANSACTION_UNDO_LOG_DELETE_PERIOD + */ + String TRANSACTION_UNDO_LOG_DELETE_PERIOD = SERVER_UNDO_PREFIX + "logDeletePeriod"; + + /** + * The constant TRANSACTION_UNDO_LOG_TABLE + */ + String TRANSACTION_UNDO_LOG_TABLE = CLIENT_UNDO_PREFIX + "logTable"; + /** + * The constant LOG_PREFIX + */ + String LOG_PREFIX = "log."; + + /** + * The constant TRANSACTION_UNDO_LOG_EXCEPTION_RATE + */ + String TRANSACTION_LOG_EXCEPTION_RATE = LOG_PREFIX + "exceptionRate"; + + /** + * The constant MAX_COMMIT_RETRY_TIMEOUT. + */ + String MAX_COMMIT_RETRY_TIMEOUT = SERVER_PREFIX + "maxCommitRetryTimeout"; + + /** + * The constant MAX_ROLLBACK_RETRY_TIMEOUT. + */ + String MAX_ROLLBACK_RETRY_TIMEOUT = SERVER_PREFIX + "maxRollbackRetryTimeout"; + + /** + * The constant ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE. + */ + String ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE = SERVER_PREFIX + "rollbackRetryTimeoutUnlockEnable"; + + /** + * the constant RETRY_DEAD_THRESHOLD + */ + String RETRY_DEAD_THRESHOLD = SERVER_PREFIX + "retryDeadThreshold"; + + /** + * The constant MIN_SERVER_POOL_SIZE. + */ + String MIN_SERVER_POOL_SIZE = TRANSPORT_PREFIX + "minServerPoolSize"; + + /** + * The constant MAX_SERVER_POOL_SIZE. + */ + String MAX_SERVER_POOL_SIZE = TRANSPORT_PREFIX + "maxServerPoolSize"; + + /** + * The constant MAX_TASK_QUEUE_SIZE. + */ + String MAX_TASK_QUEUE_SIZE = TRANSPORT_PREFIX + "maxTaskQueueSize"; + + /** + * The constant KEEP_ALIVE_TIME. + */ + String KEEP_ALIVE_TIME = TRANSPORT_PREFIX + "keepAliveTime"; + + /** + * The constant TRANSPORT_TYPE + */ + String TRANSPORT_TYPE = TRANSPORT_PREFIX + "type"; + + /** + * The constant TRANSPORT_SERVER + */ + String TRANSPORT_SERVER = TRANSPORT_PREFIX + "server"; + + /** + * The constant TRANSPORT_HEARTBEAT + */ + String TRANSPORT_HEARTBEAT = TRANSPORT_PREFIX + "heartbeat"; + + /** + * The constant THREAD_FACTORY_PREFIX + */ + String THREAD_FACTORY_PREFIX = TRANSPORT_PREFIX + "threadFactory."; + + /** + * The constant BOSS_THREAD_PREFIX + */ + String BOSS_THREAD_PREFIX = THREAD_FACTORY_PREFIX + "bossThreadPrefix"; + + /** + * The constant WORKER_THREAD_PREFIX + */ + String WORKER_THREAD_PREFIX = THREAD_FACTORY_PREFIX + "workerThreadPrefix"; + + /** + * The constant SERVER_EXECUTOR_THREAD_PREFIX + */ + String SERVER_EXECUTOR_THREAD_PREFIX = THREAD_FACTORY_PREFIX + "serverExecutorThreadPrefix"; + + /** + * The constant SHARE_BOSS_WORKER + */ + String SHARE_BOSS_WORKER = THREAD_FACTORY_PREFIX + "shareBossWorker"; + + /** + * The constant CLIENT_SELECTOR_THREAD_PREFIX + */ + String CLIENT_SELECTOR_THREAD_PREFIX = THREAD_FACTORY_PREFIX + "clientSelectorThreadPrefix"; + + /** + * The constant CLIENT_SELECTOR_THREAD_SIZE + */ + String CLIENT_SELECTOR_THREAD_SIZE = THREAD_FACTORY_PREFIX + "clientSelectorThreadSize"; + + /** + * The constant CLIENT_WORKER_THREAD_PREFIX + */ + String CLIENT_WORKER_THREAD_PREFIX = THREAD_FACTORY_PREFIX + "clientWorkerThreadPrefix"; + + /** + * The constant BOSS_THREAD_SIZE + */ + String BOSS_THREAD_SIZE = THREAD_FACTORY_PREFIX + "bossThreadSize"; + + /** + * The constant WORKER_THREAD_SIZE + */ + String WORKER_THREAD_SIZE = THREAD_FACTORY_PREFIX + "workerThreadSize"; + + /** + * The constant SHUTDOWN_PREFIX + */ + String SHUTDOWN_PREFIX = TRANSPORT_PREFIX + "shutdown."; + + /** + * The constant SHUTDOWN_WAIT + */ + String SHUTDOWN_WAIT = SHUTDOWN_PREFIX + "wait"; + + /** + * The constant ENABLE_CLIENT_BATCH_SEND_REQUEST + */ + String ENABLE_CLIENT_BATCH_SEND_REQUEST = TRANSPORT_PREFIX + "enableClientBatchSendRequest"; + + /** + * The constant DISABLE_GLOBAL_TRANSACTION. + */ + String DISABLE_GLOBAL_TRANSACTION = SERVICE_PREFIX + "disableGlobalTransaction"; + + /** + * The constant SQL_PARSER_TYPE. + */ + String SQL_PARSER_TYPE = CLIENT_RM_PREFIX + "sqlParserType"; + + /** + * The constant STORE_REDIS_MODE. + */ + String STORE_REDIS_MODE = STORE_REDIS_PREFIX + "mode"; + + /** + * The constant STORE_REDIS_HOST. + */ + String STORE_REDIS_HOST = STORE_REDIS_PREFIX + "host"; + + /** + * The constant STORE_REDIS_PORT. + */ + String STORE_REDIS_PORT = STORE_REDIS_PREFIX + "port"; + + /** + * The constant STORE_REDIS_SINGLE_PREFIX. + */ + String STORE_REDIS_SINGLE_PREFIX = STORE_REDIS_PREFIX + "single."; + + /** + * The constant STORE_REDIS_SINGLE_HOST. + */ + String STORE_REDIS_SINGLE_HOST = STORE_REDIS_SINGLE_PREFIX + "host"; + + /** + * The constant STORE_MIN_Conn. + */ + String STORE_REDIS_MIN_CONN = STORE_REDIS_PREFIX + "minConn"; + + /** + * The constant STORE_REDIS_SINGLE_PORT. + */ + String STORE_REDIS_SINGLE_PORT = STORE_REDIS_SINGLE_PREFIX + "port"; + + /** + * The constant STORE_REDIS_MAX_CONN. + */ + String STORE_REDIS_MAX_CONN = STORE_REDIS_PREFIX + "maxConn"; + + /** + * the constant STORE_REDIS_MAX_TOTAL + */ + String STORE_REDIS_MAX_TOTAL = STORE_REDIS_PREFIX + "maxTotal"; + + /** + * The constant STORE_REDIS_DATABASE. + */ + String STORE_REDIS_DATABASE = STORE_REDIS_PREFIX + "database"; + + /** + * The constant STORE_REDIS_PASSWORD. + */ + String STORE_REDIS_PASSWORD = STORE_REDIS_PREFIX + "password"; + + /** + * The constant STORE_REDIS_QUERY_LIMIT. + */ + String STORE_REDIS_QUERY_LIMIT = STORE_REDIS_PREFIX + "queryLimit"; + + /** + * The constant REDIS_SENTINEL_MODE. + */ + String REDIS_SENTINEL_MODE = "sentinel"; + + /** + * The constant REDIS_SINGLE_MODE. + */ + String REDIS_SINGLE_MODE = "single"; + + /** + * The constant STORE_REDIS_SENTINEL_PREFIX. + */ + String STORE_REDIS_SENTINEL_PREFIX = STORE_REDIS_PREFIX + "sentinel."; + + /** + * STORE_REDIS_SENTINEL_MASTERNAME. + */ + String STORE_REDIS_SENTINEL_MASTERNAME = STORE_REDIS_SENTINEL_PREFIX + "masterName"; + + /** + * STORE_REDIS_SENTINEL_HOST. + */ + String STORE_REDIS_SENTINEL_HOST = STORE_REDIS_SENTINEL_PREFIX + "sentinelHosts"; + + /** + * The constant CLIENT_DEGRADE_CHECK_PERIOD. + */ + String CLIENT_DEGRADE_CHECK_PERIOD = CLIENT_TM_PREFIX + "degradeCheckPeriod"; + + /** + * The constant CLIENT_DEGRADE_CHECK. + */ + String CLIENT_DEGRADE_CHECK = CLIENT_TM_PREFIX + "degradeCheck"; + /** + * The constant CLIENT_DEGRADE_CHECK_ALLOW_TIMES. + */ + String CLIENT_DEGRADE_CHECK_ALLOW_TIMES = CLIENT_TM_PREFIX + "degradeCheckAllowTimes"; + + /** + * The constant SEATA_ACCESS_KEY. + */ + String SEATA_ACCESS_KEY = SEATA_PREFIX + "accesskey"; + + /** + * The constant SEATA_SECRET_KEY. + */ + String SEATA_SECRET_KEY = SEATA_PREFIX + "secretkey"; + + /** + * The constant EXTRA_DATA_SPLIT_CHAR. + */ + String EXTRA_DATA_SPLIT_CHAR = "\n"; + /** + * The constant EXTRA_DATA_KV_CHAR. + */ + String EXTRA_DATA_KV_CHAR = "="; + + /** + * The constant SERVER_ENABLE_CHECK_AUTH. + */ + String SERVER_ENABLE_CHECK_AUTH = SERVER_PREFIX + "enableCheckAuth"; + + /** + * The constant APPLICATION_ID. + */ + String APPLICATION_ID = "applicationId"; + + /** + * The constant TX_SERVICE_GROUP. + */ + String TX_SERVICE_GROUP = "txServiceGroup"; + + /** + * The constant DATA_SOURCE_PROXY_MODE. + */ + String DATA_SOURCE_PROXY_MODE = "dataSourceProxyMode"; +} diff --git a/core/src/main/java/io/seata/core/constants/DBType.java b/core/src/main/java/io/seata/core/constants/DBType.java new file mode 100644 index 0000000..ec93ef7 --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/DBType.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +import io.seata.common.util.StringUtils; + +/** + * database type + * + * @author zhangsen + */ +public enum DBType { + + /** + * Mysql db type. + */ + MYSQL, + + /** + * Oracle db type. + */ + ORACLE, + + /** + * Db 2 db type. + */ + DB2, + + /** + * Sqlserver db type. + */ + SQLSERVER, + + /** + * Sybaee db type. + */ + SYBAEE, + + /** + * H2 db type. + */ + H2, + + /** + * Sqlite db type. + */ + SQLITE, + + /** + * Access db type. + */ + ACCESS, + + /** + * Postgresql db type. + */ + POSTGRESQL, + + /** + * Oceanbase db type. + */ + OCEANBASE; + + /** + * Valueof db type. + * + * @param dbType the db type + * @return the db type + */ + public static DBType valueof(String dbType) { + for (DBType dt : values()) { + if (StringUtils.equalsIgnoreCase(dt.name(), dbType)) { + return dt; + } + } + throw new IllegalArgumentException("unknown dbtype:" + dbType); + } + +} diff --git a/core/src/main/java/io/seata/core/constants/DubboConstants.java b/core/src/main/java/io/seata/core/constants/DubboConstants.java new file mode 100644 index 0000000..33aef9d --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/DubboConstants.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +/** + * DubboConstants + * + * @author funkye + */ +public class DubboConstants { + + public static final String PROVIDER = "provider"; + + public static final String CONSUMER = "consumer"; + + public static boolean ALIBABADUBBO; + + static { + try { + Class.forName("org.apache.dubbo.rpc.RpcContext"); + } catch (ClassNotFoundException e) { + ALIBABADUBBO = true; + } + } +} diff --git a/core/src/main/java/io/seata/core/constants/RedisKeyConstants.java b/core/src/main/java/io/seata/core/constants/RedisKeyConstants.java new file mode 100644 index 0000000..6887aca --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/RedisKeyConstants.java @@ -0,0 +1,144 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +/** + * The redis key constants + * + * @author wangzhongxiang + */ +public class RedisKeyConstants { + + /** + * The constant redis key of global transaction name xid + */ + public static final String REDIS_KEY_GLOBAL_XID = "xid"; + + /** + * The constant redis key of global transaction name transactionId + */ + public static final String REDIS_KEY_GLOBAL_TRANSACTION_ID = "transactionId"; + + /** + * The constant redis key of global transaction name status + */ + public static final String REDIS_KEY_GLOBAL_STATUS = "status"; + + /** + * The constant redis key of global transaction name applicationId + */ + public static final String REDIS_KEY_GLOBAL_APPLICATION_ID = "applicationId"; + + /** + * The constant redis key of global transaction name transactionServiceGroup + */ + public static final String REDIS_KEY_GLOBAL_TRANSACTION_SERVICE_GROUP = "transactionServiceGroup"; + + /** + * The constant redis key of global transaction name transactionName + */ + public static final String REDIS_KEY_GLOBAL_TRANSACTION_NAME = "transactionName"; + + /** + * The constant redis key of global transaction name timeout + */ + public static final String REDIS_KEY_GLOBAL_TIMEOUT = "timeout"; + + /** + * The constant redis key of global transaction name beginTime + */ + public static final String REDIS_KEY_GLOBAL_BEGIN_TIME = "beginTime"; + + /** + * The constant redis key of global transaction name applicationData + */ + public static final String REDIS_KEY_GLOBAL_APPLICATION_DATA = "applicationData"; + + /** + * The constant redis key of global transaction name gmtCreate + */ + public static final String REDIS_KEY_GLOBAL_GMT_CREATE = "gmtCreate"; + + /** + * The constant redis key of global transaction name gmtModified + */ + public static final String REDIS_KEY_GLOBAL_GMT_MODIFIED = "gmtModified"; + + + + + + /** + * The constant redis key of branch transaction name branchId + */ + public static final String REDIS_KEY_BRANCH_BRANCH_ID = "branchId"; + + /** + * The constant redis key of branch transaction name xid + */ + public static final String REDIS_KEY_BRANCH_XID = "xid"; + + /** + * The constant redis key of branch transaction name transactionId + */ + public static final String REDIS_KEY_BRANCH_TRANSACTION_ID = "transactionId"; + + /** + * The constant redis key of branch transaction name resourceGroupId + */ + public static final String REDIS_KEY_BRANCH_RESOURCE_GROUP_ID = "resourceGroupId"; + + /** + * The constant redis key of branch transaction name resourceId + */ + public static final String REDIS_KEY_BRANCH_RESOURCE_ID = "resourceId"; + + /**REDIS_ + * The constant redis key of branch transaction name branchType + */ + public static final String REDIS_KEY_BRANCH_BRANCH_TYPE = "branchType"; + + /** + * The constant redis key of branch transaction name status + */ + public static final String REDIS_KEY_BRANCH_STATUS = "status"; + + /** + * The constant redis key of branch transaction name beginTime + */ + public static final String REDIS_KEY_BRANCH_BEGIN_TIME = "beginTime"; + + /** + * The constant redis key of branch transaction name applicationData + */ + public static final String REDIS_KEY_BRANCH_APPLICATION_DATA = "applicationData"; + + /** + * The constant redis key of branch transaction name clientId + */ + public static final String REDIS_KEY_BRANCH_CLIENT_ID = "clientId"; + + /** + * The constant redis key of branch transaction name gmtCreate + */ + public static final String REDIS_KEY_BRANCH_GMT_CREATE = "gmtCreate"; + + /** + * The constant redis key of branch transaction name gmtModified + */ + public static final String REDIS_KEY_BRANCH_GMT_MODIFIED = "gmtModified"; + +} diff --git a/core/src/main/java/io/seata/core/constants/ServerTableColumnsName.java b/core/src/main/java/io/seata/core/constants/ServerTableColumnsName.java new file mode 100644 index 0000000..c7fe354 --- /dev/null +++ b/core/src/main/java/io/seata/core/constants/ServerTableColumnsName.java @@ -0,0 +1,195 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.constants; + +/** + * server table columns name. + * + * @author zjinlei + */ +public interface ServerTableColumnsName { + + /** + * The constant global_table column name xid + */ + String GLOBAL_TABLE_XID = "xid"; + + /** + * The constant global_table column name transaction_id + */ + String GLOBAL_TABLE_TRANSACTION_ID = "transaction_id"; + + /** + * The constant global_table column name status + */ + String GLOBAL_TABLE_STATUS = "status"; + + /** + * The constant global_table column name application_id + */ + String GLOBAL_TABLE_APPLICATION_ID = "application_id"; + + /** + * The constant global_table column name transaction_service_group + */ + String GLOBAL_TABLE_TRANSACTION_SERVICE_GROUP = "transaction_service_group"; + + /** + * The constant global_table column name transaction_name + */ + String GLOBAL_TABLE_TRANSACTION_NAME = "transaction_name"; + + /** + * The constant global_table column name timeout + */ + String GLOBAL_TABLE_TIMEOUT = "timeout"; + + /** + * The constant global_table column name begin_time + */ + String GLOBAL_TABLE_BEGIN_TIME = "begin_time"; + + /** + * The constant global_table column name application_data + */ + String GLOBAL_TABLE_APPLICATION_DATA = "application_data"; + + /** + * The constant global_table column name gmt_create + */ + String GLOBAL_TABLE_GMT_CREATE = "gmt_create"; + + /** + * The constant global_table column name gmt_modified + */ + String GLOBAL_TABLE_GMT_MODIFIED = "gmt_modified"; + + + + + + + /** + * The constant branch_table column name branch_id + */ + String BRANCH_TABLE_BRANCH_ID = "branch_id"; + + /** + * The constant branch_table column name xid + */ + String BRANCH_TABLE_XID = "xid"; + + /** + * The constant branch_table column name transaction_id + */ + String BRANCH_TABLE_TRANSACTION_ID = "transaction_id"; + + /** + * The constant branch_table column name resource_group_id + */ + String BRANCH_TABLE_RESOURCE_GROUP_ID = "resource_group_id"; + + /** + * The constant branch_table column name resource_id + */ + String BRANCH_TABLE_RESOURCE_ID = "resource_id"; + + /** + * The constant branch_table column name branch_type + */ + String BRANCH_TABLE_BRANCH_TYPE = "branch_type"; + + /** + * The constant branch_table column name status + */ + String BRANCH_TABLE_STATUS = "status"; + + /** + * The constant branch_table column name begin_time + */ + String BRANCH_TABLE_BEGIN_TIME = "begin_time"; + + /** + * The constant branch_table column name application_data + */ + String BRANCH_TABLE_APPLICATION_DATA = "application_data"; + + /** + * The constant branch_table column name client_id + */ + String BRANCH_TABLE_CLIENT_ID = "client_id"; + + /** + * The constant branch_table column name gmt_create + */ + String BRANCH_TABLE_GMT_CREATE = "gmt_create"; + + /** + * The constant branch_table column name gmt_modified + */ + String BRANCH_TABLE_GMT_MODIFIED = "gmt_modified"; + + + + + + + /** + * The constant lock_table column name row_key + */ + String LOCK_TABLE_ROW_KEY = "row_key"; + + /** + * The constant lock_table column name xid + */ + String LOCK_TABLE_XID = "xid"; + + /** + * The constant lock_table column name transaction_id + */ + String LOCK_TABLE_TRANSACTION_ID = "transaction_id"; + + /** + * The constant lock_table column name branch_id + */ + String LOCK_TABLE_BRANCH_ID = "branch_id"; + + + /** + * The constant lock_table column name resource_id + */ + String LOCK_TABLE_RESOURCE_ID = "resource_id"; + + /** + * The constant lock_table column name table_name + */ + String LOCK_TABLE_TABLE_NAME = "table_name"; + + /** + * The constant lock_table column name pk + */ + String LOCK_TABLE_PK = "pk"; + + /** + * The constant lock_table column name gmt_create + */ + String LOCK_TABLE_GMT_CREATE = "gmt_create"; + + /** + * The constant lock_table column name gmt_modified + */ + String LOCK_TABLE_GMT_MODIFIED = "gmt_modified"; +} diff --git a/core/src/main/java/io/seata/core/context/ContextCore.java b/core/src/main/java/io/seata/core/context/ContextCore.java new file mode 100644 index 0000000..8b3f2fb --- /dev/null +++ b/core/src/main/java/io/seata/core/context/ContextCore.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import javax.annotation.Nullable; +import java.util.Map; + +/** + * The interface Context core. + * + * @author sharajava + */ +public interface ContextCore { + + /** + * Put value. + * + * @param key the key + * @param value the value + * @return the previous value associated with the key, or null if there was no mapping for the key + */ + @Nullable + Object put(String key, Object value); + + /** + * Get value. + * + * @param key the key + * @return the value + */ + @Nullable + Object get(String key); + + /** + * Remove value. + * + * @param key the key + * @return the removed value or null + */ + @Nullable + Object remove(String key); + + /** + * entries + * + * @return the key-value map + */ + Map entries(); +} diff --git a/core/src/main/java/io/seata/core/context/ContextCoreLoader.java b/core/src/main/java/io/seata/core/context/ContextCoreLoader.java new file mode 100644 index 0000000..7b5ad1e --- /dev/null +++ b/core/src/main/java/io/seata/core/context/ContextCoreLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import java.util.Optional; + +import io.seata.common.loader.EnhancedServiceLoader; + +/** + * The type Context core loader. + * + * @author sharajava + */ +public class ContextCoreLoader { + + private ContextCoreLoader() { + + } + + private static class ContextCoreHolder { + private static final ContextCore INSTANCE = Optional.ofNullable(EnhancedServiceLoader.load(ContextCore.class)).orElse(new ThreadLocalContextCore()); + } + + /** + * Load context core. + * + * @return the context core + */ + public static ContextCore load() { + return ContextCoreHolder.INSTANCE; + } + +} diff --git a/core/src/main/java/io/seata/core/context/FastThreadLocalContextCore.java b/core/src/main/java/io/seata/core/context/FastThreadLocalContextCore.java new file mode 100644 index 0000000..7840f8f --- /dev/null +++ b/core/src/main/java/io/seata/core/context/FastThreadLocalContextCore.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import io.netty.util.concurrent.FastThreadLocal; +import io.seata.common.loader.LoadLevel; + +import java.util.HashMap; +import java.util.Map; + +/** + * The type Fast Thread local context core. + * + * @author ph3636 + */ +@LoadLevel(name = "FastThreadLocalContextCore", order = Integer.MIN_VALUE + 1) +public class FastThreadLocalContextCore implements ContextCore { + + private FastThreadLocal> fastThreadLocal = new FastThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap<>(); + } + }; + + @Override + public Object put(String key, Object value) { + return fastThreadLocal.get().put(key, value); + } + + @Override + public Object get(String key) { + return fastThreadLocal.get().get(key); + } + + @Override + public Object remove(String key) { + return fastThreadLocal.get().remove(key); + } + + @Override + public Map entries() { + return fastThreadLocal.get(); + } +} \ No newline at end of file diff --git a/core/src/main/java/io/seata/core/context/GlobalLockConfigHolder.java b/core/src/main/java/io/seata/core/context/GlobalLockConfigHolder.java new file mode 100644 index 0000000..bc5dd6a --- /dev/null +++ b/core/src/main/java/io/seata/core/context/GlobalLockConfigHolder.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import io.seata.core.model.GlobalLockConfig; + +/** use this class to access current GlobalLockConfig from anywhere + * @author selfishlover + */ +public class GlobalLockConfigHolder { + + private static ThreadLocal holder = new ThreadLocal<>(); + + public static GlobalLockConfig getCurrentGlobalLockConfig() { + return holder.get(); + } + + public static GlobalLockConfig setAndReturnPrevious(GlobalLockConfig config) { + GlobalLockConfig previous = holder.get(); + holder.set(config); + return previous; + } + + public static void remove() { + holder.remove(); + } +} diff --git a/core/src/main/java/io/seata/core/context/RootContext.java b/core/src/main/java/io/seata/core/context/RootContext.java new file mode 100644 index 0000000..fb51045 --- /dev/null +++ b/core/src/main/java/io/seata/core/context/RootContext.java @@ -0,0 +1,255 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.StringUtils; +import io.seata.core.model.BranchType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import static io.seata.core.model.BranchType.AT; +import static io.seata.core.model.BranchType.XA; + +/** + * The type Root context. + * + * @author slievrly + */ +public class RootContext { + + private RootContext() { + } + + private static final Logger LOGGER = LoggerFactory.getLogger(RootContext.class); + + /** + * The constant KEY_XID. + */ + public static final String KEY_XID = "TX_XID"; + + /** + * The constant MDC_KEY_XID for logback + * @since 1.5.0 + */ + public static final String MDC_KEY_XID = "X-TX-XID"; + + /** + * The constant MDC_KEY_BRANCH_ID for logback + * @since 1.5.0 + */ + public static final String MDC_KEY_BRANCH_ID = "X-TX-BRANCH-ID"; + + /** + * The constant KEY_BRANCH_TYPE + */ + public static final String KEY_BRANCH_TYPE = "TX_BRANCH_TYPE"; + + /** + * The constant KEY_GLOBAL_LOCK_FLAG, VALUE_GLOBAL_LOCK_FLAG + */ + public static final String KEY_GLOBAL_LOCK_FLAG = "TX_LOCK"; + public static final Boolean VALUE_GLOBAL_LOCK_FLAG = true; + + private static ContextCore CONTEXT_HOLDER = ContextCoreLoader.load(); + + private static BranchType DEFAULT_BRANCH_TYPE; + + public static void setDefaultBranchType(BranchType defaultBranchType) { + if (defaultBranchType != AT && defaultBranchType != XA) { + throw new IllegalArgumentException("The default branch type must be " + AT + " or " + XA + "." + + " the value of the argument is: " + defaultBranchType); + } + if (DEFAULT_BRANCH_TYPE != null && DEFAULT_BRANCH_TYPE != defaultBranchType && LOGGER.isWarnEnabled()) { + LOGGER.warn("The `{}.DEFAULT_BRANCH_TYPE` has been set repeatedly. The value changes from {} to {}", + RootContext.class.getSimpleName(), DEFAULT_BRANCH_TYPE, defaultBranchType); + } + DEFAULT_BRANCH_TYPE = defaultBranchType; + } + + /** + * Gets xid. + * + * @return the xid + */ + @Nullable + public static String getXID() { + return (String) CONTEXT_HOLDER.get(KEY_XID); + } + + /** + * Bind xid. + * + * @param xid the xid + */ + public static void bind(@Nonnull String xid) { + if (StringUtils.isBlank(xid)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid is blank, switch to unbind operation!"); + } + unbind(); + } else { + MDC.put(MDC_KEY_XID, xid); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind {}", xid); + } + CONTEXT_HOLDER.put(KEY_XID, xid); + } + } + + /** + * declare local transactions will use global lock check for update/delete/insert/selectForUpdate SQL + */ + public static void bindGlobalLockFlag() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Local Transaction Global Lock support enabled"); + } + + //just put something not null + CONTEXT_HOLDER.put(KEY_GLOBAL_LOCK_FLAG, VALUE_GLOBAL_LOCK_FLAG); + } + + /** + * Unbind xid. + * + * @return the previous xid or null + */ + @Nullable + public static String unbind() { + String xid = (String) CONTEXT_HOLDER.remove(KEY_XID); + if (xid != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind {} ", xid); + } + MDC.remove(MDC_KEY_XID); + } + return xid; + } + + public static void unbindGlobalLockFlag() { + Boolean lockFlag = (Boolean) CONTEXT_HOLDER.remove(KEY_GLOBAL_LOCK_FLAG); + if (LOGGER.isDebugEnabled() && lockFlag != null) { + LOGGER.debug("unbind global lock flag"); + } + } + + /** + * In global transaction boolean. + * + * @return the boolean + */ + public static boolean inGlobalTransaction() { + return CONTEXT_HOLDER.get(KEY_XID) != null; + } + + /** + * In tcc branch boolean. + * + * @return the boolean + */ + public static boolean inTccBranch() { + return BranchType.TCC == getBranchType(); + } + + /** + * In saga branch boolean. + * + * @return the boolean + */ + public static boolean inSagaBranch() { + return BranchType.SAGA == getBranchType(); + } + + /** + * get the branch type + * + * @return the branch type String + */ + @Nullable + public static BranchType getBranchType() { + if (inGlobalTransaction()) { + BranchType branchType = (BranchType) CONTEXT_HOLDER.get(KEY_BRANCH_TYPE); + if (branchType != null) { + return branchType; + } + //Returns the default branch type. + return DEFAULT_BRANCH_TYPE != null ? DEFAULT_BRANCH_TYPE : BranchType.AT; + } + return null; + } + + /** + * bind branch type + * + * @param branchType the branch type + */ + public static void bindBranchType(@Nonnull BranchType branchType) { + if (branchType == null) { + throw new IllegalArgumentException("branchType must be not null"); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind branch type {}", branchType); + } + + CONTEXT_HOLDER.put(KEY_BRANCH_TYPE, branchType); + } + + /** + * unbind branch type + * + * @return the previous branch type or null + */ + @Nullable + public static BranchType unbindBranchType() { + BranchType unbindBranchType = (BranchType) CONTEXT_HOLDER.remove(KEY_BRANCH_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind branch type {}", unbindBranchType); + } + return unbindBranchType; + } + + /** + * requires global lock check + * + * @return the boolean + */ + public static boolean requireGlobalLock() { + return CONTEXT_HOLDER.get(KEY_GLOBAL_LOCK_FLAG) != null; + } + + /** + * Assert not in global transaction. + */ + public static void assertNotInGlobalTransaction() { + if (inGlobalTransaction()) { + throw new ShouldNeverHappenException(); + } + } + + /** + * entry map + * + * @return the key-value map + */ + public static Map entries() { + return CONTEXT_HOLDER.entries(); + } +} diff --git a/core/src/main/java/io/seata/core/context/ThreadLocalContextCore.java b/core/src/main/java/io/seata/core/context/ThreadLocalContextCore.java new file mode 100644 index 0000000..7d75da9 --- /dev/null +++ b/core/src/main/java/io/seata/core/context/ThreadLocalContextCore.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import java.util.HashMap; +import java.util.Map; +import io.seata.common.loader.LoadLevel; + +/** + * The type Thread local context core. + * + * @author slievrly + */ +@LoadLevel(name = "ThreadLocalContextCore", order = Integer.MIN_VALUE) +public class ThreadLocalContextCore implements ContextCore { + + private ThreadLocal> threadLocal = ThreadLocal.withInitial(HashMap::new); + + @Override + public Object put(String key, Object value) { + return threadLocal.get().put(key, value); + } + + @Override + public Object get(String key) { + return threadLocal.get().get(key); + } + + @Override + public Object remove(String key) { + return threadLocal.get().remove(key); + } + + @Override + public Map entries() { + return threadLocal.get(); + } +} diff --git a/core/src/main/java/io/seata/core/event/Event.java b/core/src/main/java/io/seata/core/event/Event.java new file mode 100644 index 0000000..7e73fd7 --- /dev/null +++ b/core/src/main/java/io/seata/core/event/Event.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.event; + +/** + * The base interface for seata event. + * + * @author zhengyangyong + */ +public interface Event { +} diff --git a/core/src/main/java/io/seata/core/event/EventBus.java b/core/src/main/java/io/seata/core/event/EventBus.java new file mode 100644 index 0000000..8fb8e7d --- /dev/null +++ b/core/src/main/java/io/seata/core/event/EventBus.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.event; + +/** + * The interface for event bus. + * + * @author zhengyangyong + */ +public interface EventBus { + void register(Object subscriber); + + void unregister(Object subscriber); + + void post(Event event); +} diff --git a/core/src/main/java/io/seata/core/event/GlobalTransactionEvent.java b/core/src/main/java/io/seata/core/event/GlobalTransactionEvent.java new file mode 100644 index 0000000..bf209ce --- /dev/null +++ b/core/src/main/java/io/seata/core/event/GlobalTransactionEvent.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.event; + +import io.seata.core.model.GlobalStatus; + +/** + * Event data for global transaction. + * + * @author zhengyangyong + */ +public class GlobalTransactionEvent implements Event { + public static final String ROLE_TC = "tc"; + + public static final String ROLE_TM = "tm"; + + public static final String ROLE_RM = "rm"; + + /** + * Transaction Id + */ + private long id; + + /** + * Source Role + */ + private final String role; + + /** + * Transaction Name + */ + private final String name; + + /** + * business applicationId + */ + private String applicationId; + + /** + * Transaction Service Group + */ + private String group; + + /** + * Transaction Begin Time + */ + private final Long beginTime; + + /** + * Transaction End Time (If Transaction do not committed or rollbacked, null) + */ + private final Long endTime; + + /** + * Transaction Status + */ + private final GlobalStatus status; + + public long getId() { + return id; + } + + public String getRole() { + return role; + } + + public String getName() { + return name; + } + + public String getApplicationId() { + return applicationId; + } + + public String getGroup() { + return group; + } + + public Long getBeginTime() { + return beginTime; + } + + public Long getEndTime() { + return endTime; + } + + public GlobalStatus getStatus() { + return status; + } + + public GlobalTransactionEvent(long id, String role, String name, String applicationId, + String group, Long beginTime, Long endTime, GlobalStatus status) { + this.id = id; + this.role = role; + this.name = name; + this.applicationId = applicationId; + this.group = group; + this.beginTime = beginTime; + this.endTime = endTime; + this.status = status; + } +} diff --git a/core/src/main/java/io/seata/core/event/GuavaEventBus.java b/core/src/main/java/io/seata/core/event/GuavaEventBus.java new file mode 100644 index 0000000..8db0d8c --- /dev/null +++ b/core/src/main/java/io/seata/core/event/GuavaEventBus.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.event; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.seata.common.thread.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default event bus implement with Guava EventBus. + * + * @author zhengyangyong + */ +public class GuavaEventBus implements EventBus { + private static final Logger LOGGER = LoggerFactory.getLogger(GuavaEventBus.class); + private final com.google.common.eventbus.EventBus eventBus; + + public GuavaEventBus(String identifier) { + this(identifier, false); + } + + public GuavaEventBus(String identifier, boolean async) { + if (!async) { + this.eventBus = new com.google.common.eventbus.EventBus(identifier); + } else { + final ExecutorService eventExecutor = new ThreadPoolExecutor(1, 1, Integer.MAX_VALUE, TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(2048), new NamedThreadFactory(identifier, 1), (r, executor) -> { + + LOGGER.warn("eventBus executor queue is full, size:{}", executor.getQueue().size()); + }); + this.eventBus = new com.google.common.eventbus.AsyncEventBus(identifier, eventExecutor); + } + } + + @Override + public void register(Object subscriber) { + this.eventBus.register(subscriber); + } + + @Override + public void unregister(Object subscriber) { + this.eventBus.unregister(subscriber); + } + + @Override + public void post(Event event) { + this.eventBus.post(event); + } +} diff --git a/core/src/main/java/io/seata/core/exception/AbstractExceptionHandler.java b/core/src/main/java/io/seata/core/exception/AbstractExceptionHandler.java new file mode 100644 index 0000000..e0e949c --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/AbstractExceptionHandler.java @@ -0,0 +1,135 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.AbstractTransactionRequest; +import io.seata.core.protocol.transaction.AbstractTransactionResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Abstract exception handler. + * + * @author sharajava + */ +public abstract class AbstractExceptionHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExceptionHandler.class); + + /** + * The constant CONFIG. + */ + protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + /** + * The interface Callback. + * + * @param the type parameter + * @param the type parameter + */ + public interface Callback { + /** + * Execute. + * + * @param request the request + * @param response the response + * @throws TransactionException the transaction exception + */ + void execute(T request, S response) throws TransactionException; + + /** + * On success. + * + * @param request the request + * @param response the response + */ + void onSuccess(T request, S response); + + /** + * onTransactionException + * + * @param request the request + * @param response the response + * @param exception the exception + */ + void onTransactionException(T request, S response, TransactionException exception); + + /** + * on other exception + * + * @param request the request + * @param response the response + * @param exception the exception + */ + void onException(T request, S response, Exception exception); + + } + + /** + * The type Abstract callback. + * + * @param the type parameter + * @param the type parameter + */ + public abstract static class AbstractCallback + implements Callback { + + @Override + public void onSuccess(T request, S response) { + response.setResultCode(ResultCode.Success); + } + + @Override + public void onTransactionException(T request, S response, + TransactionException tex) { + response.setTransactionExceptionCode(tex.getCode()); + response.setResultCode(ResultCode.Failed); + response.setMsg("TransactionException[" + tex.getMessage() + "]"); + } + + @Override + public void onException(T request, S response, Exception rex) { + response.setResultCode(ResultCode.Failed); + response.setMsg("RuntimeException[" + rex.getMessage() + "]"); + } + } + + /** + * Exception handle template. + * + * @param the type parameter + * @param the type parameter + * @param callback the callback + * @param request the request + * @param response the response + */ + public void exceptionHandleTemplate(Callback callback, T request, S response) { + try { + callback.execute(request, response); + callback.onSuccess(request, response); + } catch (TransactionException tex) { + LOGGER.error("Catch TransactionException while do RPC, request: {}", request, tex); + callback.onTransactionException(request, response, tex); + } catch (RuntimeException rex) { + LOGGER.error("Catch RuntimeException while do RPC, request: {}", request, rex); + callback.onException(request, response, rex); + } + } + +} diff --git a/core/src/main/java/io/seata/core/exception/BranchTransactionException.java b/core/src/main/java/io/seata/core/exception/BranchTransactionException.java new file mode 100644 index 0000000..49e6d30 --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/BranchTransactionException.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The type BranchTransaction exception. + * + * @author will + */ +public class BranchTransactionException extends TransactionException { + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + */ + public BranchTransactionException(TransactionExceptionCode code) { + super(code); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param cause the cause + */ + public BranchTransactionException(TransactionExceptionCode code, Throwable cause) { + super(code, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + */ + public BranchTransactionException(String message) { + super(message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + */ + public BranchTransactionException(TransactionExceptionCode code, String message) { + super(code, message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param cause the cause + */ + public BranchTransactionException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + * @param cause the cause + */ + public BranchTransactionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + * @param cause the cause + */ + public BranchTransactionException(TransactionExceptionCode code, String message, Throwable cause) { + super(code, message, cause); + } +} diff --git a/core/src/main/java/io/seata/core/exception/GlobalTransactionException.java b/core/src/main/java/io/seata/core/exception/GlobalTransactionException.java new file mode 100644 index 0000000..d0ba2ba --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/GlobalTransactionException.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The type GlobalTransaction exception. + * + * @author will + */ +public class GlobalTransactionException extends TransactionException { + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + */ + public GlobalTransactionException(TransactionExceptionCode code) { + super(code); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param cause the cause + */ + public GlobalTransactionException(TransactionExceptionCode code, Throwable cause) { + super(code, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + */ + public GlobalTransactionException(String message) { + super(message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + */ + public GlobalTransactionException(TransactionExceptionCode code, String message) { + super(code, message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param cause the cause + */ + public GlobalTransactionException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + * @param cause the cause + */ + public GlobalTransactionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + * @param cause the cause + */ + public GlobalTransactionException(TransactionExceptionCode code, String message, Throwable cause) { + super(code, message, cause); + } +} diff --git a/core/src/main/java/io/seata/core/exception/RmTransactionException.java b/core/src/main/java/io/seata/core/exception/RmTransactionException.java new file mode 100644 index 0000000..2e5ee22 --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/RmTransactionException.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The type RmTransaction exception. + * + * @author will + */ +public class RmTransactionException extends BranchTransactionException { + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + */ + public RmTransactionException(TransactionExceptionCode code) { + super(code); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param cause the cause + */ + public RmTransactionException(TransactionExceptionCode code, Throwable cause) { + super(code, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + */ + public RmTransactionException(String message) { + super(message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + */ + public RmTransactionException(TransactionExceptionCode code, String message) { + super(code, message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param cause the cause + */ + public RmTransactionException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + * @param cause the cause + */ + public RmTransactionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + * @param cause the cause + */ + public RmTransactionException(TransactionExceptionCode code, String message, Throwable cause) { + super(code, message, cause); + } +} diff --git a/core/src/main/java/io/seata/core/exception/TmTransactionException.java b/core/src/main/java/io/seata/core/exception/TmTransactionException.java new file mode 100644 index 0000000..35deb9f --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/TmTransactionException.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The type TmTransaction exception. + * + * @author will + */ +public class TmTransactionException extends GlobalTransactionException { + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + */ + public TmTransactionException(TransactionExceptionCode code) { + super(code); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param cause the cause + */ + public TmTransactionException(TransactionExceptionCode code, Throwable cause) { + super(code, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + */ + public TmTransactionException(String message) { + super(message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + */ + public TmTransactionException(TransactionExceptionCode code, String message) { + super(code, message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param cause the cause + */ + public TmTransactionException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + * @param cause the cause + */ + public TmTransactionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + * @param cause the cause + */ + public TmTransactionException(TransactionExceptionCode code, String message, Throwable cause) { + super(code, message, cause); + } +} diff --git a/core/src/main/java/io/seata/core/exception/TransactionException.java b/core/src/main/java/io/seata/core/exception/TransactionException.java new file mode 100644 index 0000000..d094086 --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/TransactionException.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The type Transaction exception. + * + * @author sharajava + */ +public class TransactionException extends Exception { + + /** + * The Code. + */ + protected TransactionExceptionCode code = TransactionExceptionCode.Unknown; + + /** + * Gets code. + * + * @return the code + */ + public TransactionExceptionCode getCode() { + return code; + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + */ + public TransactionException(TransactionExceptionCode code) { + this.code = code; + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param cause the cause + */ + public TransactionException(TransactionExceptionCode code, Throwable cause) { + super(cause); + this.code = code; + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + */ + public TransactionException(String message) { + super(message); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + */ + public TransactionException(TransactionExceptionCode code, String message) { + super(message); + this.code = code; + } + + /** + * Instantiates a new Transaction exception. + * + * @param cause the cause + */ + public TransactionException(Throwable cause) { + super(cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param message the message + * @param cause the cause + */ + public TransactionException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Transaction exception. + * + * @param code the code + * @param message the message + * @param cause the cause + */ + public TransactionException(TransactionExceptionCode code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } +} diff --git a/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java b/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java new file mode 100644 index 0000000..0c106c3 --- /dev/null +++ b/core/src/main/java/io/seata/core/exception/TransactionExceptionCode.java @@ -0,0 +1,148 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.exception; + +/** + * The enum Transaction exception code. + * + * @author sharajava + */ +public enum TransactionExceptionCode { + + /** + * Unknown transaction exception code. + */ + Unknown, + + /** + * BeginFailed + */ + BeginFailed, + + /** + * Lock key conflict transaction exception code. + */ + LockKeyConflict, + + /** + * Io transaction exception code. + */ + IO, + + /** + * Branch rollback failed retriable transaction exception code. + */ + BranchRollbackFailed_Retriable, + + /** + * Branch rollback failed unretriable transaction exception code. + */ + BranchRollbackFailed_Unretriable, + + /** + * Branch register failed transaction exception code. + */ + BranchRegisterFailed, + + /** + * Branch report failed transaction exception code. + */ + BranchReportFailed, + + /** + * Lockable check failed transaction exception code. + */ + LockableCheckFailed, + + /** + * Branch transaction not exist transaction exception code. + */ + BranchTransactionNotExist, + + /** + * Global transaction not exist transaction exception code. + */ + GlobalTransactionNotExist, + + /** + * Global transaction not active transaction exception code. + */ + GlobalTransactionNotActive, + + /** + * Global transaction status invalid transaction exception code. + */ + GlobalTransactionStatusInvalid, + + /** + * Failed to send branch commit request transaction exception code. + */ + FailedToSendBranchCommitRequest, + + /** + * Failed to send branch rollback request transaction exception code. + */ + FailedToSendBranchRollbackRequest, + + /** + * Failed to add branch transaction exception code. + */ + FailedToAddBranch, + + /** + * Failed to lock global transaction exception code. + */ + FailedLockGlobalTranscation, + + /** + * FailedWriteSession + */ + FailedWriteSession, + + /** + * Failed to store exception code + */ + FailedStore + ; + + + /** + * Get transaction exception code. + * + * @param ordinal the ordinal + * @return the transaction exception code + */ + public static TransactionExceptionCode get(byte ordinal) { + return get((int)ordinal); + } + + /** + * Get transaction exception code. + * + * @param ordinal the ordinal + * @return the transaction exception code + */ + public static TransactionExceptionCode get(int ordinal) { + TransactionExceptionCode value = null; + try { + value = TransactionExceptionCode.values()[ordinal]; + } catch (Exception e) { + throw new IllegalArgumentException("Unknown TransactionExceptionCode[" + ordinal + "]"); + } + return value; + } + +} diff --git a/core/src/main/java/io/seata/core/lock/AbstractLocker.java b/core/src/main/java/io/seata/core/lock/AbstractLocker.java new file mode 100644 index 0000000..b9523e2 --- /dev/null +++ b/core/src/main/java/io/seata/core/lock/AbstractLocker.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.lock; + +import java.util.ArrayList; +import java.util.List; + +import io.seata.common.util.CollectionUtils; +import io.seata.core.store.LockDO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Abstract locker. + * + * @author zhangsen + */ +public abstract class AbstractLocker implements Locker { + + /** + * The constant LOGGER. + */ + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractLocker.class); + + /** + * The constant LOCK_SPLIT. + */ + protected static final String LOCK_SPLIT = "^^^"; + + /** + * Convert to lock do list. + * + * @param locks the locks + * @return the list + */ + protected List convertToLockDO(List locks) { + List lockDOs = new ArrayList<>(); + if (CollectionUtils.isEmpty(locks)) { + return lockDOs; + } + for (RowLock rowLock : locks) { + LockDO lockDO = new LockDO(); + lockDO.setBranchId(rowLock.getBranchId()); + lockDO.setPk(rowLock.getPk()); + lockDO.setResourceId(rowLock.getResourceId()); + lockDO.setRowKey(getRowKey(rowLock.getResourceId(), rowLock.getTableName(), rowLock.getPk())); + lockDO.setXid(rowLock.getXid()); + lockDO.setTransactionId(rowLock.getTransactionId()); + lockDO.setTableName(rowLock.getTableName()); + lockDOs.add(lockDO); + } + return lockDOs; + } + + /** + * Get row key string. + * + * @param resourceId the resource id + * @param tableName the table name + * @param pk the pk + * @return the string + */ + protected String getRowKey(String resourceId, String tableName, String pk) { + return new StringBuilder().append(resourceId).append(LOCK_SPLIT).append(tableName).append(LOCK_SPLIT).append(pk) + .toString(); + } + + @Override + public void cleanAllLocks() { + + } + + @Override + public boolean releaseLock(String xid, Long branchId) { + return false; + } + + @Override + public boolean releaseLock(String xid, List branchIds) { + return false; + } + +} diff --git a/core/src/main/java/io/seata/core/lock/LocalDBLocker.java b/core/src/main/java/io/seata/core/lock/LocalDBLocker.java new file mode 100644 index 0000000..6d0b19a --- /dev/null +++ b/core/src/main/java/io/seata/core/lock/LocalDBLocker.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.lock; + +import java.util.List; + +/** + * The type Local db locker. + * + * @author zhangsen + */ +public class LocalDBLocker extends AbstractLocker { + + @Override + public boolean acquireLock(List rowLock) { + return false; + } + + @Override + public boolean releaseLock(List rowLock) { + return false; + } + + @Override + public boolean isLockable(List rowLock) { + return false; + } +} diff --git a/core/src/main/java/io/seata/core/lock/LockMode.java b/core/src/main/java/io/seata/core/lock/LockMode.java new file mode 100644 index 0000000..27e9b12 --- /dev/null +++ b/core/src/main/java/io/seata/core/lock/LockMode.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.lock; + +/** + * lock mode + * + * @author zhangsen + */ +public enum LockMode { + + /** + * store the lock in user's database + */ + LOCAL, + + /** + * store the lock in seata's server + */ + REMOTE; + + +} diff --git a/core/src/main/java/io/seata/core/lock/Locker.java b/core/src/main/java/io/seata/core/lock/Locker.java new file mode 100644 index 0000000..2fc0ca9 --- /dev/null +++ b/core/src/main/java/io/seata/core/lock/Locker.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.lock; + +import java.util.List; + +/** + * The interface Locker. + */ +public interface Locker { + + /** + * Acquire lock boolean. + * + * @param rowLock the row lock + * @return the boolean + */ + boolean acquireLock(List rowLock) ; + + /** + * Release lock boolean. + * + * @param rowLock the row lock + * @return the boolean + */ + boolean releaseLock(List rowLock); + + /** + * Release lock boolean. + * + * @param xid the xid + * @param branchId the branch id + * @return the boolean + */ + boolean releaseLock(String xid, Long branchId); + + /** + * Release lock boolean. + * + * @param xid the xid + * @param branchIds the branch ids + * @return the boolean + */ + boolean releaseLock(String xid, List branchIds); + + /** + * Is lockable boolean. + * + * @param rowLock the row lock + * @return the boolean + */ + boolean isLockable(List rowLock); + + /** + * Clean all locks. + */ + void cleanAllLocks(); +} + diff --git a/core/src/main/java/io/seata/core/lock/RowLock.java b/core/src/main/java/io/seata/core/lock/RowLock.java new file mode 100644 index 0000000..9a41913 --- /dev/null +++ b/core/src/main/java/io/seata/core/lock/RowLock.java @@ -0,0 +1,192 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.lock; + +import io.seata.common.util.StringUtils; + +/** + * The type Row lock. + * + * @author zhangsen + */ +public class RowLock { + + private String xid; + + private Long transactionId; + + private Long branchId; + + private String resourceId; + + private String tableName; + + private String pk; + + private String rowKey; + + private String feature; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public Long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(Long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public Long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(Long branchId) { + this.branchId = branchId; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets table name. + * + * @return the table name + */ + public String getTableName() { + return tableName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets pk. + * + * @return the pk + */ + public String getPk() { + return pk; + } + + /** + * Sets pk. + * + * @param pk the pk + */ + public void setPk(String pk) { + this.pk = pk; + } + + /** + * Gets row key. + * + * @return the row key + */ + public String getRowKey() { + return rowKey; + } + + /** + * Sets row key. + * + * @param rowKey the row key + */ + public void setRowKey(String rowKey) { + this.rowKey = rowKey; + } + + /** + * Gets feature. + * + * @return the feature + */ + public String getFeature() { + return feature; + } + + /** + * Sets feature. + * + * @param feature the feature + */ + public void setFeature(String feature) { + this.feature = feature; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} + diff --git a/core/src/main/java/io/seata/core/logger/StackTraceLogger.java b/core/src/main/java/io/seata/core/logger/StackTraceLogger.java new file mode 100644 index 0000000..fe5eadc --- /dev/null +++ b/core/src/main/java/io/seata/core/logger/StackTraceLogger.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.logger; + +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; + +import io.seata.common.util.CollectionUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import org.slf4j.Logger; + +import static io.seata.common.DefaultValues.DEFAULT_LOG_EXCEPTION_RATE; + +/** + * @author jsbxyyx + */ +public final class StackTraceLogger { + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private static final String STACK_TRACE_LOGGER_PREFIX = "[stacktrace]"; + + public static void info(Logger logger, Throwable cause, String format, Object[] args) { + if (logger.isInfoEnabled()) { + if (needToPrintStackTrace()) { + logger.info(STACK_TRACE_LOGGER_PREFIX + format, buildNewArgs(args, cause)); + } else { + logger.info(format, args); + } + } + } + + public static void warn(Logger logger, Throwable cause, String format, Object[] args) { + if (logger.isWarnEnabled()) { + if (needToPrintStackTrace()) { + logger.warn(STACK_TRACE_LOGGER_PREFIX + format, buildNewArgs(args, cause)); + } else { + logger.warn(format, args); + } + } + } + + public static void error(Logger logger, Throwable cause, String format, Object[] args) { + if (logger.isErrorEnabled()) { + if (needToPrintStackTrace()) { + logger.error(STACK_TRACE_LOGGER_PREFIX + format, buildNewArgs(args, cause)); + } else { + logger.error(format, args); + } + } + } + + private static int getRate() { + return CONFIG.getInt(ConfigurationKeys.TRANSACTION_LOG_EXCEPTION_RATE, DEFAULT_LOG_EXCEPTION_RATE); + } + + private static boolean needToPrintStackTrace() { + int rate = getRate(); + return ThreadLocalRandom.current().nextInt(rate) == 0; + } + + private static Object[] buildNewArgs(Object[] args, Throwable cause) { + if (CollectionUtils.isEmpty(args)) { + return new Object[]{cause}; + } else { + Object[] newArgs = Arrays.copyOf(args, args.length + 1); + newArgs[args.length] = cause; + return newArgs; + } + } +} diff --git a/core/src/main/java/io/seata/core/model/BranchStatus.java b/core/src/main/java/io/seata/core/model/BranchStatus.java new file mode 100644 index 0000000..353c467 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/BranchStatus.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + + +import io.seata.common.exception.ShouldNeverHappenException; + +/** + * Status of branch transaction. + * + * @author sharajava + */ +public enum BranchStatus { + + /** + * The Unknown. + * description:Unknown branch status. + */ + Unknown(0), + + /** + * The Registered. + * description:Registered to TC. + */ + Registered(1), + + /** + * The Phase one done. + * description:Branch logic is successfully done at phase one. + */ + PhaseOne_Done(2), + + /** + * The Phase one failed. + * description:Branch logic is failed at phase one. + */ + PhaseOne_Failed(3), + + /** + * The Phase one timeout. + * description:Branch logic is NOT reported for a timeout. + */ + PhaseOne_Timeout(4), + + /** + * The Phase two committed. + * description:Commit logic is successfully done at phase two. + */ + PhaseTwo_Committed(5), + + /** + * The Phase two commit failed retryable. + * description:Commit logic is failed but retryable. + */ + PhaseTwo_CommitFailed_Retryable(6), + + /** + * The Phase two commit failed unretryable. + * description:Commit logic is failed and NOT retryable. + */ + PhaseTwo_CommitFailed_Unretryable(7), + + /** + * The Phase two rollbacked. + * description:Rollback logic is successfully done at phase two. + */ + PhaseTwo_Rollbacked(8), + + /** + * The Phase two rollback failed retryable. + * description:Rollback logic is failed but retryable. + */ + PhaseTwo_RollbackFailed_Retryable(9), + + /** + * The Phase two rollback failed unretryable. + * description:Rollback logic is failed but NOT retryable. + */ + PhaseTwo_RollbackFailed_Unretryable(10); + + private int code; + + BranchStatus(int code) { + this.code = code; + } + + /** + * Gets code. + * + * @return the code + */ + public int getCode() { + return code; + } + + + /** + * Get branch status. + * + * @param code the code + * @return the branch status + */ + public static BranchStatus get(byte code) { + return get((int)code); + } + + /** + * Get branch status. + * + * @param code the code + * @return the branch status + */ + public static BranchStatus get(int code) { + BranchStatus value = null; + try { + value = BranchStatus.values()[code]; + } catch (Exception e) { + throw new ShouldNeverHappenException("Unknown BranchStatus[" + code + "]"); + } + return value; + } + +} diff --git a/core/src/main/java/io/seata/core/model/BranchType.java b/core/src/main/java/io/seata/core/model/BranchType.java new file mode 100644 index 0000000..f7ecfa0 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/BranchType.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +/** + * The enum Branch type. + * + * @author sharajava + */ +public enum BranchType { + + /** + * The At. + */ + // AT Branch + AT, + + /** + * The TCC. + */ + TCC, + + /** + * The SAGA. + */ + SAGA, + + /** + * The XA. + */ + XA; + + /** + * Get branch type. + * + * @param ordinal the ordinal + * @return the branch type + */ + public static BranchType get(byte ordinal) { + return get((int)ordinal); + } + + /** + * Get branch type. + * + * @param ordinal the ordinal + * @return the branch type + */ + public static BranchType get(int ordinal) { + for (BranchType branchType : values()) { + if (branchType.ordinal() == ordinal) { + return branchType; + } + } + throw new IllegalArgumentException("Unknown BranchType[" + ordinal + "]"); + } + + /** + * Get branch type. + * + * @param name the name + * @return the branch type + */ + public static BranchType get(String name) { + for (BranchType branchType : values()) { + if (branchType.name().equalsIgnoreCase(name)) { + return branchType; + } + } + throw new IllegalArgumentException("Unknown BranchType[" + name + "]"); + } +} diff --git a/core/src/main/java/io/seata/core/model/GlobalLockConfig.java b/core/src/main/java/io/seata/core/model/GlobalLockConfig.java new file mode 100644 index 0000000..994d3a6 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/GlobalLockConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +/** + * @author selfishlover + */ +public class GlobalLockConfig { + + private int lockRetryInternal; + + private int lockRetryTimes; + + public int getLockRetryInternal() { + return lockRetryInternal; + } + + public void setLockRetryInternal(int lockRetryInternal) { + this.lockRetryInternal = lockRetryInternal; + } + + public int getLockRetryTimes() { + return lockRetryTimes; + } + + public void setLockRetryTimes(int lockRetryTimes) { + this.lockRetryTimes = lockRetryTimes; + } +} diff --git a/core/src/main/java/io/seata/core/model/GlobalStatus.java b/core/src/main/java/io/seata/core/model/GlobalStatus.java new file mode 100644 index 0000000..203c792 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/GlobalStatus.java @@ -0,0 +1,164 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + + +/** + * Status of global transaction. + * + * @author sharajava + */ +public enum GlobalStatus { + + /** + * Un known global status. + */ + // Unknown + UnKnown(0), + + /** + * The Begin. + */ + // PHASE 1: can accept new branch registering. + Begin(1), + + /** + * PHASE 2: Running Status: may be changed any time. + */ + // Committing. + Committing(2), + + /** + * The Commit retrying. + */ + // Retrying commit after a recoverable failure. + CommitRetrying(3), + + /** + * Rollbacking global status. + */ + // Rollbacking + Rollbacking(4), + + /** + * The Rollback retrying. + */ + // Retrying rollback after a recoverable failure. + RollbackRetrying(5), + + /** + * The Timeout rollbacking. + */ + // Rollbacking since timeout + TimeoutRollbacking(6), + + /** + * The Timeout rollback retrying. + */ + // Retrying rollback (since timeout) after a recoverable failure. + TimeoutRollbackRetrying(7), + + /** + * All branches can be async committed. The committing is NOT done yet, but it can be seen as committed for TM/RM + * client. + */ + AsyncCommitting(8), + + /** + * PHASE 2: Final Status: will NOT change any more. + */ + // Finally: global transaction is successfully committed. + Committed(9), + + /** + * The Commit failed. + */ + // Finally: failed to commit + CommitFailed(10), + + /** + * The Rollbacked. + */ + // Finally: global transaction is successfully rollbacked. + Rollbacked(11), + + /** + * The Rollback failed. + */ + // Finally: failed to rollback + RollbackFailed(12), + + /** + * The Timeout rollbacked. + */ + // Finally: global transaction is successfully rollbacked since timeout. + TimeoutRollbacked(13), + + /** + * The Timeout rollback failed. + */ + // Finally: failed to rollback since timeout + TimeoutRollbackFailed(14), + + /** + * The Finished. + */ + // Not managed in session MAP any more + Finished(15); + + private int code; + + GlobalStatus(int code) { + this.code = code; + } + + + /** + * Gets code. + * + * @return the code + */ + public int getCode() { + return code; + } + + + /** + * Get global status. + * + * @param code the code + * @return the global status + */ + public static GlobalStatus get(byte code) { + return get((int) code); + } + + /** + * Get global status. + * + * @param code the code + * @return the global status + */ + public static GlobalStatus get(int code) { + GlobalStatus value = null; + try { + value = GlobalStatus.values()[code]; + } catch (Exception e) { + throw new IllegalArgumentException("Unknown GlobalStatus[" + code + "]"); + } + return value; + } +} diff --git a/core/src/main/java/io/seata/core/model/Resource.java b/core/src/main/java/io/seata/core/model/Resource.java new file mode 100644 index 0000000..26958d5 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/Resource.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +/** + * Resource that can be managed by Resource Manager and involved into global transaction. + * + * @author sharajava + */ +public interface Resource { + + /** + * Get the resource group id. + * e.g. master and slave data-source should be with the same resource group id. + * + * @return resource group id. + */ + String getResourceGroupId(); + + /** + * Get the resource id. + * e.g. url of a data-source could be the id of the db data-source resource. + * + * @return resource id. + */ + String getResourceId(); + + /** + * get resource type, AT, TCC, SAGA and XA + * + * @return branch type + */ + BranchType getBranchType(); + +} diff --git a/core/src/main/java/io/seata/core/model/ResourceManager.java b/core/src/main/java/io/seata/core/model/ResourceManager.java new file mode 100644 index 0000000..2c70320 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/ResourceManager.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import java.util.Map; + +/** + * Resource Manager: common behaviors. + * + * @author sharajava + */ +public interface ResourceManager extends ResourceManagerInbound, ResourceManagerOutbound { + + /** + * Register a Resource to be managed by Resource Manager. + * + * @param resource The resource to be managed. + */ + void registerResource(Resource resource); + + /** + * Unregister a Resource from the Resource Manager. + * + * @param resource The resource to be removed. + */ + void unregisterResource(Resource resource); + + /** + * Get all resources managed by this manager. + * + * @return resourceId -- Resource Map + */ + Map getManagedResources(); + + /** + * Get the BranchType. + * + * @return The BranchType of ResourceManager. + */ + BranchType getBranchType(); +} diff --git a/core/src/main/java/io/seata/core/model/ResourceManagerInbound.java b/core/src/main/java/io/seata/core/model/ResourceManagerInbound.java new file mode 100644 index 0000000..2162bf4 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/ResourceManagerInbound.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import io.seata.core.exception.TransactionException; + +/** + * Resource Manager. + * + * Control a branch transaction commit or rollback. + * + * @author sharajava + */ +public interface ResourceManagerInbound { + + /** + * Commit a branch transaction. + * + * @param branchType the branch type + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return Status of the branch after committing. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException; + + /** + * Rollback a branch transaction. + * + * @param branchType the branch type + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return Status of the branch after rollbacking. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, String applicationData) throws TransactionException; +} diff --git a/core/src/main/java/io/seata/core/model/ResourceManagerOutbound.java b/core/src/main/java/io/seata/core/model/ResourceManagerOutbound.java new file mode 100644 index 0000000..a833003 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/ResourceManagerOutbound.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import io.seata.core.exception.TransactionException; + +/** + * Resource Manager: send outbound request to TC. + * + * @author sharajava + */ +public interface ResourceManagerOutbound { + + /** + * Branch register long. + * + * @param branchType the branch type + * @param resourceId the resource id + * @param clientId the client id + * @param xid the xid + * @param applicationData the context + * @param lockKeys the lock keys + * @return the long + * @throws TransactionException the transaction exception + */ + Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws + TransactionException; + + /** + * Branch report. + * + * @param branchType the branch type + * @param xid the xid + * @param branchId the branch id + * @param status the status + * @param applicationData the application data + * @throws TransactionException the transaction exception + */ + void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException; + + /** + * Lock query boolean. + * + * @param branchType the branch type + * @param resourceId the resource id + * @param xid the xid + * @param lockKeys the lock keys + * @return the boolean + * @throws TransactionException the transaction exception + */ + boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys) + throws TransactionException; +} diff --git a/core/src/main/java/io/seata/core/model/Result.java b/core/src/main/java/io/seata/core/model/Result.java new file mode 100644 index 0000000..fe9832f --- /dev/null +++ b/core/src/main/java/io/seata/core/model/Result.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +/** + * Generic return result class + * + * @author zjinlei + */ +public class Result { + + private T result; + + private String errMsg; + + private Object[] errMsgParams; + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public Object[] getErrMsgParams() { + return errMsgParams; + } + + public void setErrMsgParams(Object[] errMsgParams) { + this.errMsgParams = errMsgParams; + } + + public Result(T result, String errMsg, Object[] errMsgParams) { + this.result = result; + this.errMsg = errMsg; + this.errMsgParams = errMsgParams; + } + + public static Result ok() { + return new Result<>(true, null, null); + } + + public static Result build(T result) { + return new Result(result, null, null); + } + + public static Result build(T result, String errMsg) { + return new Result(result, errMsg, null); + } + + public static Result buildWithParams(T result, String errMsg, Object... args) { + return new Result(result, errMsg, args); + } +} diff --git a/core/src/main/java/io/seata/core/model/TransactionManager.java b/core/src/main/java/io/seata/core/model/TransactionManager.java new file mode 100644 index 0000000..8628b53 --- /dev/null +++ b/core/src/main/java/io/seata/core/model/TransactionManager.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import io.seata.core.exception.TransactionException; + +/** + * Transaction Manager. + * + * Define a global transaction and control it. + * + * @author sharajava + */ +public interface TransactionManager { + + /** + * Begin a new global transaction. + * + * @param applicationId ID of the application who begins this transaction. + * @param transactionServiceGroup ID of the transaction service group. + * @param name Give a name to the global transaction. + * @param timeout Timeout of the global transaction. + * @return XID of the global transaction + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + String begin(String applicationId, String transactionServiceGroup, String name, int timeout) + throws TransactionException; + + /** + * Global commit. + * + * @param xid XID of the global transaction. + * @return Status of the global transaction after committing. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + GlobalStatus commit(String xid) throws TransactionException; + + /** + * Global rollback. + * + * @param xid XID of the global transaction + * @return Status of the global transaction after rollbacking. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + GlobalStatus rollback(String xid) throws TransactionException; + + /** + * Get current status of the give transaction. + * + * @param xid XID of the global transaction. + * @return Current status of the global transaction. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + GlobalStatus getStatus(String xid) throws TransactionException; + + /** + * Global report. + * + * @param xid XID of the global transaction. + * @param globalStatus Status of the global transaction. + * @return Status of the global transaction. + * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown + * out. + */ + GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException; +} diff --git a/core/src/main/java/io/seata/core/protocol/AbstractIdentifyRequest.java b/core/src/main/java/io/seata/core/protocol/AbstractIdentifyRequest.java new file mode 100644 index 0000000..46cf4de --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/AbstractIdentifyRequest.java @@ -0,0 +1,141 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + + +/** + * The type Abstract identify request. + * + * @author sharajava + */ +public abstract class AbstractIdentifyRequest extends AbstractMessage { + + /** + * The Version. + */ + protected String version = Version.getCurrent(); + + /** + * The Application id. + */ + protected String applicationId; + + /** + * The Transaction service group. + */ + protected String transactionServiceGroup; + + /** + * The Extra data. + */ + protected String extraData; + + /** + * Instantiates a new Abstract identify request. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + */ + public AbstractIdentifyRequest(String applicationId, String transactionServiceGroup) { + this(applicationId, transactionServiceGroup, null); + } + + /** + * Instantiates a new Abstract identify request. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @param extraData the extra data + */ + public AbstractIdentifyRequest(String applicationId, String transactionServiceGroup, String extraData) { + this.applicationId = applicationId; + this.transactionServiceGroup = transactionServiceGroup; + this.extraData = extraData; + } + + /** + * Gets version. + * + * @return the version + */ + public String getVersion() { + return version; + } + + /** + * Sets version. + * + * @param version the version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Gets application id. + * + * @return the application id + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Gets transaction service group. + * + * @return the transaction service group + */ + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + /** + * Sets transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Gets extra data. + * + * @return the extra data + */ + public String getExtraData() { + return extraData; + } + + /** + * Sets extra data. + * + * @param extraData the extra data + */ + public void setExtraData(String extraData) { + this.extraData = extraData; + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/AbstractIdentifyResponse.java b/core/src/main/java/io/seata/core/protocol/AbstractIdentifyResponse.java new file mode 100644 index 0000000..564d8ea --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/AbstractIdentifyResponse.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The type Abstract identify response. + * + * @author sharajava + */ +public abstract class AbstractIdentifyResponse extends AbstractResultMessage { + + private String version = Version.getCurrent(); + + private String extraData; + + private boolean identified; + + /** + * Gets version. + * + * @return the version + */ + public String getVersion() { + return version; + } + + /** + * Sets version. + * + * @param version the version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Gets extra data. + * + * @return the extra data + */ + public String getExtraData() { + return extraData; + } + + /** + * Sets extra data. + * + * @param extraData the extra data + */ + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + /** + * Is identified boolean. + * + * @return the boolean + */ + public boolean isIdentified() { + return identified; + } + + /** + * Sets identified. + * + * @param identified the identified + */ + public void setIdentified(boolean identified) { + this.identified = identified; + } + + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("version="); + result.append(version); + result.append(","); + result.append("extraData="); + result.append(extraData); + result.append(","); + result.append("identified="); + result.append(identified); + result.append(","); + result.append("resultCode="); + result.append(getResultCode()); + result.append(","); + result.append("msg="); + result.append(getMsg()); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/AbstractMessage.java b/core/src/main/java/io/seata/core/protocol/AbstractMessage.java new file mode 100644 index 0000000..2a3dbc2 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/AbstractMessage.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.Constants; +import io.seata.common.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * The type Abstract message. + * + * @author slievrly + */ +public abstract class AbstractMessage implements MessageTypeAware, Serializable { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractMessage.class); + + protected static final long serialVersionUID = -1441020418526899889L; + + /** + * The constant UTF8. + */ + protected static final Charset UTF8 = Constants.DEFAULT_CHARSET; + /** + * The Ctx. + */ + protected ChannelHandlerContext ctx; + + /** + * Bytes to int int. + * + * @param bytes the bytes + * @param offset the offset + * @return the int + */ + public static int bytesToInt(byte[] bytes, int offset) { + int ret = 0; + for (int i = 0; i < 4 && i + offset < bytes.length; i++) { + ret <<= 8; + ret |= (int)bytes[i + offset] & 0xFF; + } + return ret; + } + + /** + * Int to bytes. + * + * @param i the + * @param bytes the bytes + * @param offset the offset + */ + public static void intToBytes(int i, byte[] bytes, int offset) { + bytes[offset] = (byte)((i >> 24) & 0xFF); + bytes[offset + 1] = (byte)((i >> 16) & 0xFF); + bytes[offset + 2] = (byte)((i >> 8) & 0xFF); + bytes[offset + 3] = (byte)(i & 0xFF); + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/AbstractResultMessage.java b/core/src/main/java/io/seata/core/protocol/AbstractResultMessage.java new file mode 100644 index 0000000..9b0922b --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/AbstractResultMessage.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The type Abstract result message. + * + * @author slievrly + */ +public abstract class AbstractResultMessage extends AbstractMessage { + + private ResultCode resultCode; + + private String msg; + + /** + * Gets result code. + * + * @return the result code + */ + public ResultCode getResultCode() { + return resultCode; + } + + /** + * Sets result code. + * + * @param resultCode the result code + */ + public void setResultCode(ResultCode resultCode) { + this.resultCode = resultCode; + } + + /** + * Gets msg. + * + * @return the msg + */ + public String getMsg() { + return msg; + } + + /** + * Sets msg. + * + * @param msg the msg + */ + public void setMsg(String msg) { + this.msg = msg; + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/HeartbeatMessage.java b/core/src/main/java/io/seata/core/protocol/HeartbeatMessage.java new file mode 100644 index 0000000..e3e8eb8 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/HeartbeatMessage.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; + +/** + * The type Heartbeat message. + * + * @author slievrly + */ +public class HeartbeatMessage implements MessageTypeAware, Serializable { + private static final long serialVersionUID = -985316399527884899L; + private boolean ping = true; + /** + * The constant PING. + */ + public static final HeartbeatMessage PING = new HeartbeatMessage(true); + /** + * The constant PONG. + */ + public static final HeartbeatMessage PONG = new HeartbeatMessage(false); + + private HeartbeatMessage(boolean ping) { + this.ping = ping; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_HEARTBEAT_MSG; + } + + @Override + public String toString() { + return this.ping ? "services ping" : "services pong"; + } + + public boolean isPing() { + return ping; + } + + public void setPing(boolean ping) { + this.ping = ping; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/IncompatibleVersionException.java b/core/src/main/java/io/seata/core/protocol/IncompatibleVersionException.java new file mode 100644 index 0000000..3fb5539 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/IncompatibleVersionException.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The type Incompatible version exception. + * + * @author sharajava + */ +public class IncompatibleVersionException extends Exception { + + /** + * Instantiates a new Incompatible version exception. + * + * @param message the message + */ + public IncompatibleVersionException(String message) { + super(message); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/MergeMessage.java b/core/src/main/java/io/seata/core/protocol/MergeMessage.java new file mode 100644 index 0000000..f54db37 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MergeMessage.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The interface Merge message. + * + * @author slievrly + */ +public interface MergeMessage { +} diff --git a/core/src/main/java/io/seata/core/protocol/MergeResultMessage.java b/core/src/main/java/io/seata/core/protocol/MergeResultMessage.java new file mode 100644 index 0000000..efda659 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MergeResultMessage.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + + +/** + * The type Merge result message. + * + * @author slievrly + */ +public class MergeResultMessage extends AbstractMessage implements MergeMessage { + + /** + * The Msgs. + */ + public AbstractResultMessage[] msgs; + + /** + * Get msgs abstract result message [ ]. + * + * @return the abstract result message [ ] + */ + public AbstractResultMessage[] getMsgs() { + return msgs; + } + + /** + * Sets msgs. + * + * @param msgs the msgs + */ + public void setMsgs(AbstractResultMessage[] msgs) { + this.msgs = msgs; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_SEATA_MERGE_RESULT; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("MergeResultMessage "); + if (msgs == null) { + return sb.toString(); + } + for (AbstractMessage msg : msgs) { sb.append(msg.toString()).append("\n"); } + return sb.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/MergedWarpMessage.java b/core/src/main/java/io/seata/core/protocol/MergedWarpMessage.java new file mode 100644 index 0000000..ed42616 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MergedWarpMessage.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * The type Merged warp message. + * + * @author slievrly + */ +public class MergedWarpMessage extends AbstractMessage implements Serializable, MergeMessage { + + /** + * The Msgs. + */ + public List msgs = new ArrayList<>(); + /** + * The Msg ids. + */ + public List msgIds = new ArrayList<>(); + + @Override + public short getTypeCode() { + return MessageType.TYPE_SEATA_MERGE; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("SeataMergeMessage "); + for (AbstractMessage msg : msgs) { + sb.append(msg.toString()).append("\n"); + } + return sb.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/MessageFuture.java b/core/src/main/java/io/seata/core/protocol/MessageFuture.java new file mode 100644 index 0000000..403237e --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MessageFuture.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.seata.common.exception.ShouldNeverHappenException; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * The type Message future. + * + * @author slievrly + */ +public class MessageFuture { + private RpcMessage requestMessage; + private long timeout; + private long start = System.currentTimeMillis(); + private transient CompletableFuture origin = new CompletableFuture<>(); + + /** + * Is timeout boolean. + * + * @return the boolean + */ + public boolean isTimeout() { + return System.currentTimeMillis() - start > timeout; + } + + /** + * Get object. + * + * @param timeout the timeout + * @param unit the unit + * @return the object + * @throws TimeoutException the timeout exception + * @throws InterruptedException the interrupted exception + */ + public Object get(long timeout, TimeUnit unit) throws TimeoutException, + InterruptedException { + Object result = null; + try { + result = origin.get(timeout, unit); + } catch (ExecutionException e) { + throw new ShouldNeverHappenException("Should not get results in a multi-threaded environment", e); + } catch (TimeoutException e) { + throw new TimeoutException("cost " + (System.currentTimeMillis() - start) + " ms"); + } + + if (result instanceof RuntimeException) { + throw (RuntimeException)result; + } else if (result instanceof Throwable) { + throw new RuntimeException((Throwable)result); + } + + return result; + } + + /** + * Sets result message. + * + * @param obj the obj + */ + public void setResultMessage(Object obj) { + origin.complete(obj); + } + + /** + * Gets request message. + * + * @return the request message + */ + public RpcMessage getRequestMessage() { + return requestMessage; + } + + /** + * Sets request message. + * + * @param requestMessage the request message + */ + public void setRequestMessage(RpcMessage requestMessage) { + this.requestMessage = requestMessage; + } + + /** + * Gets timeout. + * + * @return the timeout + */ + public long getTimeout() { + return timeout; + } + + /** + * Sets timeout. + * + * @param timeout the timeout + */ + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/MessageType.java b/core/src/main/java/io/seata/core/protocol/MessageType.java new file mode 100644 index 0000000..846adc2 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MessageType.java @@ -0,0 +1,141 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The type Message codec type. + * + * @author zhangsen + */ +public interface MessageType { + + /** + * The constant TYPE_GLOBAL_BEGIN. + */ + short TYPE_GLOBAL_BEGIN = 1; + /** + * The constant TYPE_GLOBAL_BEGIN_RESULT. + */ + short TYPE_GLOBAL_BEGIN_RESULT = 2; + /** + * The constant TYPE_GLOBAL_COMMIT. + */ + short TYPE_GLOBAL_COMMIT = 7; + /** + * The constant TYPE_GLOBAL_COMMIT_RESULT. + */ + short TYPE_GLOBAL_COMMIT_RESULT = 8; + /** + * The constant TYPE_GLOBAL_ROLLBACK. + */ + short TYPE_GLOBAL_ROLLBACK = 9; + /** + * The constant TYPE_GLOBAL_ROLLBACK_RESULT. + */ + short TYPE_GLOBAL_ROLLBACK_RESULT = 10; + /** + * The constant TYPE_GLOBAL_STATUS. + */ + short TYPE_GLOBAL_STATUS = 15; + /** + * The constant TYPE_GLOBAL_STATUS_RESULT. + */ + short TYPE_GLOBAL_STATUS_RESULT = 16; + /** + * The constant TYPE_GLOBAL_REPORT. + */ + short TYPE_GLOBAL_REPORT = 17; + /** + * The constant TYPE_GLOBAL_REPORT_RESULT. + */ + short TYPE_GLOBAL_REPORT_RESULT = 18; + /** + * The constant TYPE_GLOBAL_LOCK_QUERY. + */ + short TYPE_GLOBAL_LOCK_QUERY = 21; + /** + * The constant TYPE_GLOBAL_LOCK_QUERY_RESULT. + */ + short TYPE_GLOBAL_LOCK_QUERY_RESULT = 22; + + /** + * The constant TYPE_BRANCH_COMMIT. + */ + short TYPE_BRANCH_COMMIT = 3; + /** + * The constant TYPE_BRANCH_COMMIT_RESULT. + */ + short TYPE_BRANCH_COMMIT_RESULT = 4; + /** + * The constant TYPE_BRANCH_ROLLBACK. + */ + short TYPE_BRANCH_ROLLBACK = 5; + /** + * The constant TYPE_BRANCH_ROLLBACK_RESULT. + */ + short TYPE_BRANCH_ROLLBACK_RESULT = 6; + /** + * The constant TYPE_BRANCH_REGISTER. + */ + short TYPE_BRANCH_REGISTER = 11; + /** + * The constant TYPE_BRANCH_REGISTER_RESULT. + */ + short TYPE_BRANCH_REGISTER_RESULT = 12; + /** + * The constant TYPE_BRANCH_STATUS_REPORT. + */ + short TYPE_BRANCH_STATUS_REPORT = 13; + /** + * The constant TYPE_BRANCH_STATUS_REPORT_RESULT. + */ + short TYPE_BRANCH_STATUS_REPORT_RESULT = 14; + + /** + * The constant TYPE_SEATA_MERGE. + */ + short TYPE_SEATA_MERGE = 59; + /** + * The constant TYPE_SEATA_MERGE_RESULT. + */ + short TYPE_SEATA_MERGE_RESULT = 60; + + /** + * The constant TYPE_REG_CLT. + */ + short TYPE_REG_CLT = 101; + /** + * The constant TYPE_REG_CLT_RESULT. + */ + short TYPE_REG_CLT_RESULT = 102; + /** + * The constant TYPE_REG_RM. + */ + short TYPE_REG_RM = 103; + /** + * The constant TYPE_REG_RM_RESULT. + */ + short TYPE_REG_RM_RESULT = 104; + /** + * The constant TYPE_RM_DELETE_UNDOLOG. + */ + short TYPE_RM_DELETE_UNDOLOG = 111; + + /** + * the constant TYPE_HEARTBEAT_MSG + */ + short TYPE_HEARTBEAT_MSG = 120; +} diff --git a/core/src/main/java/io/seata/core/protocol/MessageTypeAware.java b/core/src/main/java/io/seata/core/protocol/MessageTypeAware.java new file mode 100644 index 0000000..dd62080 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/MessageTypeAware.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The interface Message type aware. + */ +public interface MessageTypeAware { + + /** + * Gets type code. + * + * @return the type code + */ + short getTypeCode(); + +} diff --git a/core/src/main/java/io/seata/core/protocol/ProtocolConstants.java b/core/src/main/java/io/seata/core/protocol/ProtocolConstants.java new file mode 100644 index 0000000..ef5d9a4 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/ProtocolConstants.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.seata.config.ConfigurationFactory; +import io.seata.core.serializer.SerializerType; +import io.seata.core.compressor.CompressorType; +import io.seata.core.constants.ConfigurationKeys; + +/** + * @author Geng Zhang + * @since 0.7.0 + */ +public interface ProtocolConstants { + + /** + * Magic code + */ + byte[] MAGIC_CODE_BYTES = {(byte) 0xda, (byte) 0xda}; + + /** + * Protocol version + */ + byte VERSION = 1; + + /** + * Max frame length + */ + int MAX_FRAME_LENGTH = 8 * 1024 * 1024; + + /** + * HEAD_LENGTH of protocol v1 + */ + int V1_HEAD_LENGTH = 16; + + /** + * Message type: Request + */ + byte MSGTYPE_RESQUEST_SYNC = 0; + /** + * Message type: Response + */ + byte MSGTYPE_RESPONSE = 1; + /** + * Message type: Request which no need response + */ + byte MSGTYPE_RESQUEST_ONEWAY = 2; + /** + * Message type: Heartbeat Request + */ + byte MSGTYPE_HEARTBEAT_REQUEST = 3; + /** + * Message type: Heartbeat Response + */ + byte MSGTYPE_HEARTBEAT_RESPONSE = 4; + + //byte MSGTYPE_NEGOTIATOR_REQUEST = 5; + //byte MSGTYPE_NEGOTIATOR_RESPONSE = 6; + + /** + * Configured codec by user, default is SEATA + * + * @see SerializerType#SEATA + */ + byte CONFIGURED_CODEC = SerializerType.getByName(ConfigurationFactory.getInstance() + .getConfig(ConfigurationKeys.SERIALIZE_FOR_RPC, SerializerType.SEATA.name())).getCode(); + + /** + * Configured compressor by user, default is NONE + * + * @see CompressorType#NONE + */ + byte CONFIGURED_COMPRESSOR = CompressorType.getByName(ConfigurationFactory.getInstance() + .getConfig(ConfigurationKeys.COMPRESSOR_FOR_RPC, CompressorType.NONE.name())).getCode(); +} diff --git a/core/src/main/java/io/seata/core/protocol/RegisterRMRequest.java b/core/src/main/java/io/seata/core/protocol/RegisterRMRequest.java new file mode 100644 index 0000000..21dfb30 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/RegisterRMRequest.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; + +/** + * The type Register rm request. + * + * @author slievrly + */ +public class RegisterRMRequest extends AbstractIdentifyRequest implements Serializable { + + /** + * Instantiates a new Register rm request. + */ + public RegisterRMRequest() { + this(null, null); + } + + /** + * Instantiates a new Register rm request. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + */ + public RegisterRMRequest(String applicationId, String transactionServiceGroup) { + super(applicationId, transactionServiceGroup); + } + + private String resourceIds; + + /** + * Gets resource ids. + * + * @return the resource ids + */ + public String getResourceIds() { + return resourceIds; + } + + /** + * Sets resource ids. + * + * @param resourceIds the resource ids + */ + public void setResourceIds(String resourceIds) { + this.resourceIds = resourceIds; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_REG_RM; + } + + @Override + public String toString() { + return "RegisterRMRequest{" + + "resourceIds='" + resourceIds + '\'' + + ", applicationId='" + applicationId + '\'' + + ", transactionServiceGroup='" + transactionServiceGroup + '\'' + + '}'; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/RegisterRMResponse.java b/core/src/main/java/io/seata/core/protocol/RegisterRMResponse.java new file mode 100644 index 0000000..f590084 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/RegisterRMResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; + +/** + * The type Register rm response. + * + * @author slievrly + */ +public class RegisterRMResponse extends AbstractIdentifyResponse implements Serializable { + + /** + * Instantiates a new Register rm response. + */ + public RegisterRMResponse() { + this(true); + } + + /** + * Instantiates a new Register rm response. + * + * @param result the result + */ + public RegisterRMResponse(boolean result) { + super(); + setIdentified(result); + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_REG_RM_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/RegisterTMRequest.java b/core/src/main/java/io/seata/core/protocol/RegisterTMRequest.java new file mode 100644 index 0000000..7fa3132 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/RegisterTMRequest.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; + +import io.seata.common.util.NetUtil; +import org.apache.commons.lang.StringUtils; + +import static io.seata.core.constants.ConfigurationKeys.EXTRA_DATA_SPLIT_CHAR; + +/** + * The type Register tm request. + * + * @author slievrly + */ +public class RegisterTMRequest extends AbstractIdentifyRequest implements Serializable { + private static final long serialVersionUID = -5929081344190543690L; + public static final String UDATA_VGROUP = "vgroup"; + public static final String UDATA_AK = "ak"; + public static final String UDATA_DIGEST = "digest"; + public static final String UDATA_IP = "ip"; + public static final String UDATA_TIMESTAMP = "timestamp"; + + /** + * Instantiates a new Register tm request. + */ + public RegisterTMRequest() { + this(null, null); + } + + /** + * Instantiates a new Register tm request. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @param extraData the extra data + */ + public RegisterTMRequest(String applicationId, String transactionServiceGroup, String extraData) { + super(applicationId, transactionServiceGroup, extraData); + StringBuilder sb = new StringBuilder(); + if (null != extraData) { + sb.append(extraData); + if (!extraData.endsWith(EXTRA_DATA_SPLIT_CHAR)) { + sb.append(EXTRA_DATA_SPLIT_CHAR); + } + } + if (transactionServiceGroup != null && !transactionServiceGroup.isEmpty()) { + sb.append(String.format("%s=%s", UDATA_VGROUP, transactionServiceGroup)); + sb.append(EXTRA_DATA_SPLIT_CHAR); + String clientIP = NetUtil.getLocalIp(); + if (!StringUtils.isEmpty(clientIP)) { + sb.append(String.format("%s=%s", UDATA_IP, clientIP)); + sb.append(EXTRA_DATA_SPLIT_CHAR); + } + } + this.extraData = sb.toString(); + + } + + /** + * Instantiates a new Register tm request. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + */ + public RegisterTMRequest(String applicationId, String transactionServiceGroup) { + this(applicationId, transactionServiceGroup, null); + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_REG_CLT; + } + + @Override + public String toString() { + return "RegisterTMRequest{" + + "applicationId='" + applicationId + '\'' + + ", transactionServiceGroup='" + transactionServiceGroup + '\'' + + '}'; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/RegisterTMResponse.java b/core/src/main/java/io/seata/core/protocol/RegisterTMResponse.java new file mode 100644 index 0000000..e109c36 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/RegisterTMResponse.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.io.Serializable; + +/** + * The type Register tm response. + * + * @author slievrly + */ +public class RegisterTMResponse extends AbstractIdentifyResponse implements Serializable { + + /** + * Instantiates a new Register tm response. + */ + public RegisterTMResponse() { + this(true); + } + + /** + * Instantiates a new Register tm response. + * + * @param result the result + */ + public RegisterTMResponse(boolean result) { + super(); + setIdentified(result); + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_REG_CLT_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/ResultCode.java b/core/src/main/java/io/seata/core/protocol/ResultCode.java new file mode 100644 index 0000000..aea5d09 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/ResultCode.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +/** + * The enum Result code. + * + * @author sharajava + */ +public enum ResultCode { + + /** + * Failed result code. + */ + // Failed + Failed, + + /** + * Success result code. + */ + // Success + Success; + + /** + * Get result code. + * + * @param ordinal the ordinal + * @return the result code + */ + public static ResultCode get(byte ordinal) { + return get((int)ordinal); + } + + /** + * Get result code. + * + * @param ordinal the ordinal + * @return the result code + */ + public static ResultCode get(int ordinal) { + for (ResultCode resultCode : ResultCode.values()) { + if (resultCode.ordinal() == ordinal) { + return resultCode; + } + } + throw new IllegalArgumentException("Unknown ResultCode[" + ordinal + "]"); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/RpcMessage.java b/core/src/main/java/io/seata/core/protocol/RpcMessage.java new file mode 100644 index 0000000..02d611c --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/RpcMessage.java @@ -0,0 +1,175 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.seata.common.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * The type Rpc message. + * + * @author slievrly + */ +public class RpcMessage { + + private int id; + private byte messageType; + private byte codec; + private byte compressor; + private Map headMap = new HashMap<>(); + private Object body; + + /** + * Gets id. + * + * @return the id + */ + public int getId() { + return id; + } + + /** + * Sets id. + * + * @param id the id + */ + public void setId(int id) { + this.id = id; + } + + /** + * Gets body. + * + * @return the body + */ + public Object getBody() { + return body; + } + + /** + * Sets body. + * + * @param body the body + */ + public void setBody(Object body) { + this.body = body; + } + + /** + * Gets codec. + * + * @return the codec + */ + public byte getCodec() { + return codec; + } + + /** + * Sets codec. + * + * @param codec the codec + * @return the codec + */ + public RpcMessage setCodec(byte codec) { + this.codec = codec; + return this; + } + + /** + * Gets compressor. + * + * @return the compressor + */ + public byte getCompressor() { + return compressor; + } + + /** + * Sets compressor. + * + * @param compressor the compressor + * @return the compressor + */ + public RpcMessage setCompressor(byte compressor) { + this.compressor = compressor; + return this; + } + + /** + * Gets head map. + * + * @return the head map + */ + public Map getHeadMap() { + return headMap; + } + + /** + * Sets head map. + * + * @param headMap the head map + * @return the head map + */ + public RpcMessage setHeadMap(Map headMap) { + this.headMap = headMap; + return this; + } + + /** + * Gets head. + * + * @param headKey the head key + * @return the head + */ + public String getHead(String headKey) { + return headMap.get(headKey); + } + + /** + * Put head. + * + * @param headKey the head key + * @param headValue the head value + */ + public void putHead(String headKey, String headValue) { + headMap.put(headKey, headValue); + } + + /** + * Gets message type. + * + * @return the message type + */ + public byte getMessageType() { + return messageType; + } + + /** + * Sets message type. + * + * @param messageType the message type + */ + public void setMessageType(byte messageType) { + this.messageType = messageType; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/Version.java b/core/src/main/java/io/seata/core/protocol/Version.java new file mode 100644 index 0000000..f03f308 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/Version.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.netty.channel.Channel; +import io.seata.common.util.NetUtil; +import org.apache.commons.lang.StringUtils; + +/** + * The type Version. + * + * @author slievrly + */ +public class Version { + + /** + * The constant CURRENT. + */ + private static final String CURRENT = "1.4.2"; + private static final String VERSION_0_7_1 = "0.7.1"; + private static final int MAX_VERSION_DOT = 3; + + /** + * The constant VERSION_MAP. + */ + public static final Map VERSION_MAP = new ConcurrentHashMap<>(); + + private Version() { + + } + + /** + * Gets current. + * + * @return the current + */ + public static String getCurrent() { + return CURRENT; + } + + /** + * Put channel version. + * + * @param c the c + * @param v the v + */ + public static void putChannelVersion(Channel c, String v) { + VERSION_MAP.put(NetUtil.toStringAddress(c.remoteAddress()), v); + } + + /** + * Gets channel version. + * + * @param c the c + * @return the channel version + */ + public static String getChannelVersion(Channel c) { + return VERSION_MAP.get(NetUtil.toStringAddress(c.remoteAddress())); + } + + /** + * Check version string. + * + * @param version the version + * @throws IncompatibleVersionException the incompatible version exception + */ + public static void checkVersion(String version) throws IncompatibleVersionException { + long current = convertVersion(CURRENT); + long clientVersion = convertVersion(version); + long divideVersion = convertVersion(VERSION_0_7_1); + if ((current > divideVersion && clientVersion < divideVersion) || (current < divideVersion && clientVersion > divideVersion)) { + throw new IncompatibleVersionException("incompatible client version:" + version); + } + } + + private static long convertVersion(String version) throws IncompatibleVersionException { + String[] parts = StringUtils.split(version, '.'); + long result = 0L; + int i = 1; + int size = parts.length; + if (size > MAX_VERSION_DOT + 1) { + throw new IncompatibleVersionException("incompatible version format:" + version); + } + size = MAX_VERSION_DOT + 1; + for (String part : parts) { + if (StringUtils.isNumeric(part)) { + result += calculatePartValue(part, size, i); + } else { + String[] subParts = StringUtils.split(part, '-'); + if (StringUtils.isNumeric(subParts[0])) { + result += calculatePartValue(subParts[0], size, i); + } + } + + i++; + } + return result; + } + + private static long calculatePartValue(String partNumeric, int size, int index) { + return Long.parseLong(partNumeric) * Double.valueOf(Math.pow(100, size - index)).longValue(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndRequest.java new file mode 100644 index 0000000..1f077ed --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndRequest.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchType; + +/** + * The type Abstract branch end request. + * + * @author sharajava + */ +public abstract class AbstractBranchEndRequest extends AbstractTransactionRequestToRM { + + /** + * The Xid. + */ + protected String xid; + + /** + * The Branch id. + */ + protected long branchId; + + /** + * The Branch type. + */ + protected BranchType branchType = BranchType.AT; + + /** + * The Resource id. + */ + protected String resourceId; + + /** + * The Application data. + */ + protected String applicationData; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets branch type. + * + * @return the branch type + */ + public BranchType getBranchType() { + return branchType; + } + + /** + * Sets branch type. + * + * @param branchType the branch type + */ + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("xid="); + result.append(xid); + result.append(","); + result.append("branchId="); + result.append(branchId); + result.append(","); + result.append("branchType="); + result.append(branchType); + result.append(","); + result.append("resourceId="); + result.append(resourceId); + result.append(","); + result.append("applicationData="); + result.append(applicationData); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndResponse.java new file mode 100644 index 0000000..5d3b037 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractBranchEndResponse.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchStatus; + +/** + * The type Abstract branch end response. + * + * @author sharajava + */ +public abstract class AbstractBranchEndResponse extends AbstractTransactionResponse { + + /** + * The Xid. + */ + protected String xid; + + /** + * The Branch id. + */ + protected long branchId; + /** + * The Branch status. + */ + protected BranchStatus branchStatus; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets branch status. + * + * @return the branch status + */ + public BranchStatus getBranchStatus() { + return branchStatus; + } + + /** + * Sets branch status. + * + * @param branchStatus the branch status + */ + public void setBranchStatus(BranchStatus branchStatus) { + this.branchStatus = branchStatus; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("xid="); + result.append(xid); + result.append(","); + result.append("branchId="); + result.append(branchId); + result.append(","); + result.append("branchStatus="); + result.append(branchStatus); + result.append(","); + result.append("result code ="); + result.append(getResultCode()); + result.append(","); + result.append("getMsg ="); + result.append(getMsg()); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndRequest.java new file mode 100644 index 0000000..85a8a09 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +/** + * The type Abstract global end request. + * + * @author sharajava + */ +public abstract class AbstractGlobalEndRequest extends AbstractTransactionRequestToTC { + + private String xid; + + /** + * The Extra data. + */ + protected String extraData; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets extra data. + * + * @return the extra data + */ + public String getExtraData() { + return extraData; + } + + /** + * Sets extra data. + * + * @param extraData the extra data + */ + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("xid="); + result.append(xid); + result.append(","); + result.append("extraData="); + result.append(extraData); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndResponse.java new file mode 100644 index 0000000..cd6ac51 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractGlobalEndResponse.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +import io.seata.core.model.GlobalStatus; + +/** + * The type Abstract global end response. + * + * @author sharajava + */ +public abstract class AbstractGlobalEndResponse extends AbstractTransactionResponse { + + /** + * The Global status. + */ + protected GlobalStatus globalStatus; + + /** + * Gets global status. + * + * @return the global status + */ + public GlobalStatus getGlobalStatus() { + return globalStatus; + } + + /** + * Sets global status. + * + * @param globalStatus the global status + */ + public void setGlobalStatus(GlobalStatus globalStatus) { + this.globalStatus = globalStatus; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("globalStatus="); + result.append(globalStatus); + result.append(","); + result.append("ResultCode="); + result.append(getResultCode()); + result.append(","); + result.append("Msg="); + result.append(getMsg()); + + return result.toString(); + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java new file mode 100644 index 0000000..f996f2a --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.rpc.RpcContext; + +/** + * The type Abstract transaction request. + * + * @author sharajava + */ +public abstract class AbstractTransactionRequest extends AbstractMessage { + + /** + * Handle abstract transaction response. + * + * @param rpcContext the rpc context + * @return the abstract transaction response + */ + public abstract AbstractTransactionResponse handle(RpcContext rpcContext); +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToRM.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToRM.java new file mode 100644 index 0000000..cab1015 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToRM.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +/** + * The type Abstract transaction request to rm. + * + * @author sharajava + */ +public abstract class AbstractTransactionRequestToRM extends AbstractTransactionRequest { + + /** + * The Handler. + */ + protected RMInboundHandler handler; + + /** + * Sets rm inbound message handler. + * + * @param handler the handler + */ + public void setRMInboundMessageHandler(RMInboundHandler handler) { + this.handler = handler; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToTC.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToTC.java new file mode 100644 index 0000000..21d8435 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionRequestToTC.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +/** + * The type Abstract transaction request to tc. + * + * @author sharajava + */ +public abstract class AbstractTransactionRequestToTC extends AbstractTransactionRequest { + + /** + * The Handler. + */ + protected TCInboundHandler handler; + + /** + * Sets tc inbound handler. + * + * @param handler the handler + */ + public void setTCInboundHandler(TCInboundHandler handler) { + this.handler = handler; + } +} \ No newline at end of file diff --git a/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionResponse.java new file mode 100644 index 0000000..e7d3b1f --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/AbstractTransactionResponse.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.protocol.AbstractResultMessage; + +/** + * The type Abstract transaction response. + * + * @author sharajava + */ +public abstract class AbstractTransactionResponse extends AbstractResultMessage { + + private TransactionExceptionCode transactionExceptionCode = TransactionExceptionCode.Unknown; + + /** + * Gets transaction exception code. + * + * @return the transaction exception code + */ + public TransactionExceptionCode getTransactionExceptionCode() { + return transactionExceptionCode; + } + + /** + * Sets transaction exception code. + * + * @param transactionExceptionCode the transaction exception code + */ + public void setTransactionExceptionCode(TransactionExceptionCode transactionExceptionCode) { + this.transactionExceptionCode = transactionExceptionCode; + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitRequest.java new file mode 100644 index 0000000..c5ed400 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitRequest.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Branch commit request. + * + * @author sharajava + */ +public class BranchCommitRequest extends AbstractBranchEndRequest { + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_COMMIT; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this); + } + + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitResponse.java new file mode 100644 index 0000000..6788373 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchCommitResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Branch commit response. + * + * @author sharajava + */ +public class BranchCommitResponse extends AbstractBranchEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_COMMIT_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterRequest.java new file mode 100644 index 0000000..4f7155a --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterRequest.java @@ -0,0 +1,156 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchType; +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Branch register request. + * + * @author sharajava + */ +public class BranchRegisterRequest extends AbstractTransactionRequestToTC { + + private String xid; + + private BranchType branchType = BranchType.AT; + + private String resourceId; + + private String lockKey; + + private String applicationData; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch type. + * + * @return the branch type + */ + public BranchType getBranchType() { + return branchType; + } + + /** + * Sets branch type. + * + * @param branchType the branch type + */ + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + /** + * Gets lock key. + * + * @return the lock key + */ + public String getLockKey() { + return lockKey; + } + + /** + * Sets lock key. + * + * @param lockKey the lock key + */ + public void setLockKey(String lockKey) { + this.lockKey = lockKey; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_REGISTER; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("xid="); + result.append(xid); + result.append(","); + result.append("branchType="); + result.append(branchType); + result.append(","); + result.append("resourceId="); + result.append(resourceId); + result.append(","); + result.append("lockKey="); + result.append(lockKey); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterResponse.java new file mode 100644 index 0000000..9572907 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchRegisterResponse.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import java.io.Serializable; + +import io.seata.core.protocol.MessageType; + +/** + * The type Branch register response. + * + * @author slievrly + */ +public class BranchRegisterResponse extends AbstractTransactionResponse implements Serializable { + + private long branchId; + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_REGISTER_RESULT; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("BranchRegisterResponse: branchId="); + result.append(branchId); + result.append(","); + result.append("result code ="); + result.append(getResultCode()); + result.append(","); + result.append("getMsg ="); + result.append(getMsg()); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchReportRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchReportRequest.java new file mode 100644 index 0000000..2478499 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchReportRequest.java @@ -0,0 +1,181 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.rpc.RpcContext; +import io.seata.core.protocol.MessageType; + +/** + * The type Branch report request. + * + * @author slievrly + */ +public class BranchReportRequest extends AbstractTransactionRequestToTC { + + private String xid; + + private long branchId; + + private String resourceId; + + private BranchStatus status; + + private String applicationData; + + private BranchType branchType = BranchType.AT; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets branch type. + * + * @return the branch type + */ + public BranchType getBranchType() { + return branchType; + } + + /** + * Sets branch type. + * + * @param branchType the branch type + */ + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + /** + * Gets status. + * + * @return the status + */ + public BranchStatus getStatus() { + return status; + } + + /** + * Sets status. + * + * @param status the status + */ + public void setStatus(BranchStatus status) { + this.status = status; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_STATUS_REPORT; + } + + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("xid="); + result.append(xid); + result.append(","); + result.append("branchId="); + result.append(branchId); + result.append(","); + result.append("resourceId="); + result.append(resourceId); + result.append(","); + result.append("status="); + result.append(status); + result.append(","); + result.append("applicationData="); + result.append(applicationData); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchReportResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchReportResponse.java new file mode 100644 index 0000000..ce619fb --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchReportResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Branch report response. + * + * @author slievrly + */ +public class BranchReportResponse extends AbstractTransactionResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackRequest.java new file mode 100644 index 0000000..886b138 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackRequest.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Branch rollback request. + * + * @author slievrly + */ +public class BranchRollbackRequest extends AbstractBranchEndRequest { + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_ROLLBACK; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackResponse.java new file mode 100644 index 0000000..5bad478 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/BranchRollbackResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Branch rollback response. + * + * @author slievrly + */ +public class BranchRollbackResponse extends AbstractBranchEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_BRANCH_ROLLBACK_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginRequest.java new file mode 100644 index 0000000..8c9e8bb --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginRequest.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global begin request. + * + * @author slievrly + */ +public class GlobalBeginRequest extends AbstractTransactionRequestToTC { + + private int timeout = 60000; + + private String transactionName; + + /** + * Gets timeout. + * + * @return the timeout + */ + public int getTimeout() { + return timeout; + } + + /** + * Sets timeout. + * + * @param timeout the timeout + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Gets transaction name. + * + * @return the transaction name + */ + public String getTransactionName() { + return transactionName; + } + + /** + * Sets transaction name. + * + * @param transactionName the transaction name + */ + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_BEGIN; + } + + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("timeout="); + result.append(timeout); + result.append(","); + result.append("transactionName="); + result.append(transactionName); + + return result.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginResponse.java new file mode 100644 index 0000000..32c9843 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalBeginResponse.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Global begin response. + * + * @author slievrly + */ +public class GlobalBeginResponse extends AbstractTransactionResponse { + + private String xid; + + private String extraData; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets extra data. + * + * @return the extra data + */ + public String getExtraData() { + return extraData; + } + + /** + * Sets extra data. + * + * @param extraData the extra data + */ + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_BEGIN_RESULT; + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitRequest.java new file mode 100644 index 0000000..ed127e2 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global commit request. + * + * @author slievrly + */ +public class GlobalCommitRequest extends AbstractGlobalEndRequest { + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_COMMIT; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitResponse.java new file mode 100644 index 0000000..4e32f11 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalCommitResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Global commit response. + * + * @author slievrly + */ +public class GlobalCommitResponse extends AbstractGlobalEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_COMMIT_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryRequest.java new file mode 100644 index 0000000..9253404 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryRequest.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global lock query request. + * + * @author slievrly + */ +public class GlobalLockQueryRequest extends BranchRegisterRequest { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_LOCK_QUERY; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryResponse.java new file mode 100644 index 0000000..d4e69ab --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalLockQueryResponse.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + + +/** + * The type Global lock query response. + * + * @author slievrly + */ +public class GlobalLockQueryResponse extends AbstractTransactionResponse { + + private boolean lockable = false; + + /** + * Is lockable boolean. + * + * @return the boolean + */ + public boolean isLockable() { + return lockable; + } + + /** + * Sets lockable. + * + * @param lockable the lockable + */ + public void setLockable(boolean lockable) { + this.lockable = lockable; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_LOCK_QUERY_RESULT; + } + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportRequest.java new file mode 100644 index 0000000..44c3a44 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportRequest.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.GlobalStatus; +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global report request. + * + * @author lorne.cl + */ +public class GlobalReportRequest extends AbstractGlobalEndRequest { + + /** + * The Global status. + */ + protected GlobalStatus globalStatus; + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_REPORT; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } + + /** + * Gets global status. + * + * @return the global status + */ + public GlobalStatus getGlobalStatus() { + return globalStatus; + } + + /** + * Sets global status. + * + * @param globalStatus the global status + */ + public void setGlobalStatus(GlobalStatus globalStatus) { + this.globalStatus = globalStatus; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportResponse.java new file mode 100644 index 0000000..a17a43f --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalReportResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Global report response. + * + * @author lorne.cl + */ +public class GlobalReportResponse extends AbstractGlobalEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_REPORT_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackRequest.java new file mode 100644 index 0000000..47b980e --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global rollback request. + * + * @author slievrly + */ +public class GlobalRollbackRequest extends AbstractGlobalEndRequest { + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_ROLLBACK; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackResponse.java new file mode 100644 index 0000000..6fdd04f --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalRollbackResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Global rollback response. + * + * @author slievrly + */ +public class GlobalRollbackResponse extends AbstractGlobalEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_ROLLBACK_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusRequest.java new file mode 100644 index 0000000..dd37b45 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusRequest.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +/** + * The type Global status request. + * + * @author slievrly + */ +public class GlobalStatusRequest extends AbstractGlobalEndRequest { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_STATUS; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + return handler.handle(this, rpcContext); + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusResponse.java b/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusResponse.java new file mode 100644 index 0000000..b5ec353 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/GlobalStatusResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; + +/** + * The type Global status response. + * + * @author slievrly + */ +public class GlobalStatusResponse extends AbstractGlobalEndResponse { + + @Override + public short getTypeCode() { + return MessageType.TYPE_GLOBAL_STATUS_RESULT; + } +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java b/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java new file mode 100644 index 0000000..f3fb209 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/RMInboundHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +/** + * The interface Rm inbound handler. + * + * @author sharajava + */ +public interface RMInboundHandler { + + /** + * Handle branch commit response. + * + * @param request the request + * @return the branch commit response + */ + BranchCommitResponse handle(BranchCommitRequest request); + + /** + * Handle branch rollback response. + * + * @param request the request + * @return the branch rollback response + */ + BranchRollbackResponse handle(BranchRollbackRequest request); + + /** + * Handle delete undo log . + * + * @param request the request + */ + void handle(UndoLogDeleteRequest request); +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/TCInboundHandler.java b/core/src/main/java/io/seata/core/protocol/transaction/TCInboundHandler.java new file mode 100644 index 0000000..75d5066 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/TCInboundHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.rpc.RpcContext; + +/** + * The interface Tc inbound handler. + * + * @author sharajava + */ +public interface TCInboundHandler { + + /** + * Handle global begin response. + * + * @param globalBegin the global begin + * @param rpcContext the rpc context + * @return the global begin response + */ + GlobalBeginResponse handle(GlobalBeginRequest globalBegin, RpcContext rpcContext); + + /** + * Handle global commit response. + * + * @param globalCommit the global commit + * @param rpcContext the rpc context + * @return the global commit response + */ + GlobalCommitResponse handle(GlobalCommitRequest globalCommit, RpcContext rpcContext); + + /** + * Handle global rollback response. + * + * @param globalRollback the global rollback + * @param rpcContext the rpc context + * @return the global rollback response + */ + GlobalRollbackResponse handle(GlobalRollbackRequest globalRollback, RpcContext rpcContext); + + /** + * Handle branch register response. + * + * @param branchRegister the branch register + * @param rpcContext the rpc context + * @return the branch register response + */ + BranchRegisterResponse handle(BranchRegisterRequest branchRegister, RpcContext rpcContext); + + /** + * Handle branch report response. + * + * @param branchReport the branch report + * @param rpcContext the rpc context + * @return the branch report response + */ + BranchReportResponse handle(BranchReportRequest branchReport, RpcContext rpcContext); + + /** + * Handle global lock query response. + * + * @param checkLock the check lock + * @param rpcContext the rpc context + * @return the global lock query response + */ + GlobalLockQueryResponse handle(GlobalLockQueryRequest checkLock, RpcContext rpcContext); + + /** + * Handle global status response. + * + * @param globalStatus the global status + * @param rpcContext the rpc context + * @return the global status response + */ + GlobalStatusResponse handle(GlobalStatusRequest globalStatus, RpcContext rpcContext); + + /** + * Handle global report request. + * + * @param globalReport the global report request + * @param rpcContext the rpc context + * @return the global report response + */ + GlobalReportResponse handle(GlobalReportRequest globalReport, RpcContext rpcContext); + +} diff --git a/core/src/main/java/io/seata/core/protocol/transaction/UndoLogDeleteRequest.java b/core/src/main/java/io/seata/core/protocol/transaction/UndoLogDeleteRequest.java new file mode 100644 index 0000000..1244c76 --- /dev/null +++ b/core/src/main/java/io/seata/core/protocol/transaction/UndoLogDeleteRequest.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchType; +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.RpcContext; + +import java.io.Serializable; + +/** + * The type to delete undolog request. + * + * @author github-ygy + */ +public class UndoLogDeleteRequest extends AbstractTransactionRequestToRM implements Serializable { + + private static final long serialVersionUID = 7539732523682335742L; + + public static final short DEFAULT_SAVE_DAYS = 7; + + private String resourceId; + + private short saveDays = DEFAULT_SAVE_DAYS; + + /** + * The Branch type. + */ + protected BranchType branchType = BranchType.AT; + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public short getSaveDays() { + return saveDays; + } + + public void setSaveDays(short saveDays) { + this.saveDays = saveDays; + } + + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + @Override + public AbstractTransactionResponse handle(RpcContext rpcContext) { + handler.handle(this); + return null; + } + + @Override + public short getTypeCode() { + return MessageType.TYPE_RM_DELETE_UNDOLOG; + } + + @Override + public String toString() { + return "UndoLogDeleteRequest{" + + "resourceId='" + resourceId + '\'' + + ", saveDays=" + saveDays + + ", branchType=" + branchType + + '}'; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/ClientMessageListener.java b/core/src/main/java/io/seata/core/rpc/ClientMessageListener.java new file mode 100644 index 0000000..2467e3a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ClientMessageListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.seata.core.protocol.RpcMessage; + +/** + * The interface Client message listener. + * + * @author slievrly + */ +@Deprecated +public interface ClientMessageListener { + /** + * On message. + * + * @param request the msg id + * @param serverAddress the server address + */ + void onMessage(RpcMessage request, String serverAddress); +} diff --git a/core/src/main/java/io/seata/core/rpc/ClientMessageSender.java b/core/src/main/java/io/seata/core/rpc/ClientMessageSender.java new file mode 100644 index 0000000..e1edc87 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ClientMessageSender.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.seata.core.protocol.RpcMessage; + +import java.util.concurrent.TimeoutException; + +/** + * The interface Client message sender. + * + * @author slievrly + */ +@Deprecated +public interface ClientMessageSender { + /** + * Send msg with response object. + * + * @param msg the msg + * @param timeout the timeout + * @return the object + * @throws TimeoutException the timeout exception + */ + Object sendMsgWithResponse(Object msg, long timeout) throws TimeoutException; + + /** + * Send msg with response object. + * + * @param serverAddress the server address + * @param msg the msg + * @param timeout the timeout + * @return the object + * @throws TimeoutException the timeout exception + */ + Object sendMsgWithResponse(String serverAddress, Object msg, long timeout) throws TimeoutException; + + /** + * Send msg with response object. + * + * @param msg the msg + * @return the object + * @throws TimeoutException the timeout exception + */ + Object sendMsgWithResponse(Object msg) throws TimeoutException; + + /** + * Send response. + * + * @param request the msg id + * @param serverAddress the server address + * @param msg the msg + */ + void sendResponse(RpcMessage request, String serverAddress, Object msg); +} diff --git a/core/src/main/java/io/seata/core/rpc/ClientType.java b/core/src/main/java/io/seata/core/rpc/ClientType.java new file mode 100644 index 0000000..658989a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ClientType.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * The enum Client type. + * + * @author slievrly + */ +@Deprecated +public enum ClientType { + + /** + * The Tm. + */ + // Transaction Manager client + TM, + + /** + * The Rm. + */ + // Resource Manager client + RM; + + /** + * Get client type. + * + * @param ordinal the ordinal + * @return the client type + */ + public static ClientType get(byte ordinal) { + return get((int)ordinal); + } + + /** + * Get client type. + * + * @param ordinal the ordinal + * @return the client type + */ + public static ClientType get(int ordinal) { + for (ClientType clientType : ClientType.values()) { + if (clientType.ordinal() == ordinal) { + return clientType; + } + } + throw new IllegalArgumentException("Unknown ClientType[" + ordinal + "]"); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/DefaultServerMessageListenerImpl.java b/core/src/main/java/io/seata/core/rpc/DefaultServerMessageListenerImpl.java new file mode 100644 index 0000000..c81f78f --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/DefaultServerMessageListenerImpl.java @@ -0,0 +1,241 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.AbstractResultMessage; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.MergeResultMessage; +import io.seata.core.protocol.MergedWarpMessage; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.protocol.RegisterTMRequest; +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.Version; +import io.seata.core.rpc.netty.ChannelManager; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * The type Default server message listener. + * + * @author slievrly + */ +@Deprecated +public class DefaultServerMessageListenerImpl implements ServerMessageListener { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultServerMessageListenerImpl.class); + private static BlockingQueue logQueue = new LinkedBlockingQueue<>(); + private RemotingServer remotingServer; + private final TransactionMessageHandler transactionMessageHandler; + private static final int MAX_LOG_SEND_THREAD = 1; + private static final int MAX_LOG_TAKE_SIZE = 1024; + private static final long KEEP_ALIVE_TIME = 0L; + private static final String THREAD_PREFIX = "batchLoggerPrint"; + private static final long BUSY_SLEEP_MILLS = 5L; + + /** + * Instantiates a new Default server message listener. + * + * @param transactionMessageHandler the transaction message handler + */ + public DefaultServerMessageListenerImpl(TransactionMessageHandler transactionMessageHandler) { + this.transactionMessageHandler = transactionMessageHandler; + } + + @Override + public void onTrxMessage(RpcMessage request, ChannelHandlerContext ctx) { + Object message = request.getBody(); + RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("server received:{},clientIp:{},vgroup:{}", message, + NetUtil.toIpAddress(ctx.channel().remoteAddress()), rpcContext.getTransactionServiceGroup()); + } else { + try { + logQueue.put(message + ",clientIp:" + NetUtil.toIpAddress(ctx.channel().remoteAddress()) + ",vgroup:" + + rpcContext.getTransactionServiceGroup()); + } catch (InterruptedException e) { + LOGGER.error("put message to logQueue error: {}", e.getMessage(), e); + } + } + if (!(message instanceof AbstractMessage)) { + return; + } + if (message instanceof MergedWarpMessage) { + AbstractResultMessage[] results = new AbstractResultMessage[((MergedWarpMessage) message).msgs.size()]; + for (int i = 0; i < results.length; i++) { + final AbstractMessage subMessage = ((MergedWarpMessage) message).msgs.get(i); + results[i] = transactionMessageHandler.onRequest(subMessage, rpcContext); + } + MergeResultMessage resultMessage = new MergeResultMessage(); + resultMessage.setMsgs(results); + getServerMessageSender().sendAsyncResponse(request, ctx.channel(), resultMessage); + } else if (message instanceof AbstractResultMessage) { + transactionMessageHandler.onResponse((AbstractResultMessage) message, rpcContext); + } else { + // the single send request message + final AbstractMessage msg = (AbstractMessage) message; + AbstractResultMessage result = transactionMessageHandler.onRequest(msg, rpcContext); + getServerMessageSender().sendAsyncResponse(request, ctx.channel(), result); + } + } + + @Override + public void onRegRmMessage(RpcMessage request, ChannelHandlerContext ctx, RegisterCheckAuthHandler checkAuthHandler) { + RegisterRMRequest message = (RegisterRMRequest)request.getBody(); + String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + boolean isSuccess = false; + String errorInfo = StringUtils.EMPTY; + try { + if (checkAuthHandler == null || checkAuthHandler.regResourceManagerCheckAuth(message)) { + ChannelManager.registerRMChannel(message, ctx.channel()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + isSuccess = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("checkAuth for client:{},vgroup:{},applicationId:{} is OK", ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId()); + } + } + } catch (Exception exx) { + isSuccess = false; + errorInfo = exx.getMessage(); + LOGGER.error("RM register fail, error message:{}", errorInfo); + } + RegisterRMResponse response = new RegisterRMResponse(isSuccess); + if (StringUtils.isNotEmpty(errorInfo)) { + response.setMsg(errorInfo); + } + getServerMessageSender().sendAsyncResponse(request, ctx.channel(), response); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("RM register success,message:{},channel:{},client version:{}", message, ctx.channel(), + message.getVersion()); + } + } + + @Override + public void onRegTmMessage(RpcMessage request, ChannelHandlerContext ctx, RegisterCheckAuthHandler checkAuthHandler) { + RegisterTMRequest message = (RegisterTMRequest)request.getBody(); + String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + boolean isSuccess = false; + String errorInfo = StringUtils.EMPTY; + try { + if (checkAuthHandler == null || checkAuthHandler.regTransactionManagerCheckAuth(message)) { + ChannelManager.registerTMChannel(message, ctx.channel()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + isSuccess = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("checkAuth for client:{},vgroup:{},applicationId:{} is OK", ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId()); + } + } + } catch (Exception exx) { + isSuccess = false; + errorInfo = exx.getMessage(); + LOGGER.error("TM register fail, error message:{}", errorInfo); + } + RegisterTMResponse response = new RegisterTMResponse(isSuccess); + if (StringUtils.isNotEmpty(errorInfo)) { + response.setMsg(errorInfo); + } + getServerMessageSender().sendAsyncResponse(request, ctx.channel(), response); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("TM register success,message:{},channel:{},client version:{}", message, ctx.channel(), + message.getVersion()); + } + } + + @Override + public void onCheckMessage(RpcMessage request, ChannelHandlerContext ctx) { + try { + getServerMessageSender().sendAsyncResponse(request, ctx.channel(), HeartbeatMessage.PONG); + } catch (Throwable throwable) { + LOGGER.error("send response error: {}", throwable.getMessage(), throwable); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("received PING from {}", ctx.channel().remoteAddress()); + } + } + + /** + * Init. + */ + public void init() { + ExecutorService mergeSendExecutorService = new ThreadPoolExecutor(MAX_LOG_SEND_THREAD, MAX_LOG_SEND_THREAD, + KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), + new NamedThreadFactory(THREAD_PREFIX, MAX_LOG_SEND_THREAD, true)); + mergeSendExecutorService.submit(new BatchLogRunnable()); + } + + /** + * Gets server message sender. + * + * @return the server message sender + */ + public RemotingServer getServerMessageSender() { + if (remotingServer == null) { + throw new IllegalArgumentException("serverMessageSender must not be null"); + } + return remotingServer; + } + + /** + * Sets server message sender. + * + * @param remotingServer the remoting server + */ + public void setServerMessageSender(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + } + + /** + * The type Batch log runnable. + */ + static class BatchLogRunnable implements Runnable { + + @Override + public void run() { + List logList = new ArrayList<>(); + while (true) { + try { + logList.add(logQueue.take()); + logQueue.drainTo(logList, MAX_LOG_TAKE_SIZE); + if (LOGGER.isInfoEnabled()) { + for (String str : logList) { + LOGGER.info(str); + } + } + logList.clear(); + TimeUnit.MILLISECONDS.sleep(BUSY_SLEEP_MILLS); + } catch (InterruptedException exx) { + LOGGER.error("batch log busy sleep error:{}", exx.getMessage(), exx); + } + + } + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/Disposable.java b/core/src/main/java/io/seata/core/rpc/Disposable.java new file mode 100644 index 0000000..0370419 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/Disposable.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * + * @author 563868273@qq.com + */ +public interface Disposable { + + void destroy(); + +} diff --git a/core/src/main/java/io/seata/core/rpc/RegisterCheckAuthHandler.java b/core/src/main/java/io/seata/core/rpc/RegisterCheckAuthHandler.java new file mode 100644 index 0000000..a398f45 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RegisterCheckAuthHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterTMRequest; + +/** + * The interface Register check auth handler. + * + * @author slievrly + */ +public interface RegisterCheckAuthHandler { + + /** + * Reg transaction manager check auth boolean. + * + * @param request the request + * @return the boolean + */ + boolean regTransactionManagerCheckAuth(RegisterTMRequest request); + + /** + * Reg resource manager check auth boolean. + * + * @param request the request + * @return the boolean + */ + boolean regResourceManagerCheckAuth(RegisterRMRequest request); +} diff --git a/core/src/main/java/io/seata/core/rpc/RemotingBootstrap.java b/core/src/main/java/io/seata/core/rpc/RemotingBootstrap.java new file mode 100644 index 0000000..b280e6e --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RemotingBootstrap.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * The boot strap of the remoting process, generally there are client and server implementation classes + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public interface RemotingBootstrap { + + /** + * Start. + */ + void start(); + + /** + * Shutdown. + */ + void shutdown(); + +} diff --git a/core/src/main/java/io/seata/core/rpc/RemotingClient.java b/core/src/main/java/io/seata/core/rpc/RemotingClient.java new file mode 100644 index 0000000..a2a32a4 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RemotingClient.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.Channel; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.netty.NettyClientConfig; +import io.seata.core.rpc.processor.RemotingProcessor; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeoutException; + +/** + * The interface remoting client. + * + * @author zhaojun + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public interface RemotingClient { + + /** + * client send sync request. + * In this request, if {@link NettyClientConfig#isEnableClientBatchSendRequest} is enabled, + * the message will be sent in batches. + * + * @param msg transaction message {@link io.seata.core.protocol} + * @return server result message + * @throws TimeoutException TimeoutException + */ + Object sendSyncRequest(Object msg) throws TimeoutException; + + /** + * client send sync request. + * + * @param channel client channel + * @param msg transaction message {@link io.seata.core.protocol} + * @return server result message + * @throws TimeoutException TimeoutException + */ + Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException; + + /** + * client send async request. + * + * @param channel client channel + * @param msg transaction message {@link io.seata.core.protocol} + */ + void sendAsyncRequest(Channel channel, Object msg); + + /** + * client send async response. + * + * @param serverAddress server address + * @param rpcMessage rpc message from server request + * @param msg transaction message {@link io.seata.core.protocol} + */ + void sendAsyncResponse(String serverAddress, RpcMessage rpcMessage, Object msg); + + /** + * On register msg success. + * + * @param serverAddress the server address + * @param channel the channel + * @param response the response + * @param requestMessage the request message + */ + void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response, AbstractMessage requestMessage); + + /** + * On register msg fail. + * + * @param serverAddress the server address + * @param channel the channel + * @param response the response + * @param requestMessage the request message + */ + void onRegisterMsgFail(String serverAddress, Channel channel, Object response, AbstractMessage requestMessage); + + /** + * register processor + * + * @param messageType {@link io.seata.core.protocol.MessageType} + * @param processor {@link RemotingProcessor} + * @param executor thread pool + */ + void registerProcessor(final int messageType, final RemotingProcessor processor, final ExecutorService executor); +} diff --git a/core/src/main/java/io/seata/core/rpc/RemotingServer.java b/core/src/main/java/io/seata/core/rpc/RemotingServer.java new file mode 100644 index 0000000..3db95b7 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RemotingServer.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.Channel; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.processor.RemotingProcessor; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeoutException; + +/** + * The interface Remoting server. + * + * @author slievrly + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public interface RemotingServer { + + /** + * server send sync request. + * + * @param resourceId rm client resourceId + * @param clientId rm client id + * @param msg transaction message {@link io.seata.core.protocol} + * @return client result message + * @throws TimeoutException TimeoutException + */ + Object sendSyncRequest(String resourceId, String clientId, Object msg) throws TimeoutException; + + /** + * server send sync request. + * + * @param channel client channel + * @param msg transaction message {@link io.seata.core.protocol} + * @return client result message + * @throws TimeoutException TimeoutException + */ + Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException; + + /** + * server send async request. + * + * @param channel client channel + * @param msg transaction message {@link io.seata.core.protocol} + */ + void sendAsyncRequest(Channel channel, Object msg); + + /** + * server send async response. + * + * @param rpcMessage rpc message from client request + * @param channel client channel + * @param msg transaction message {@link io.seata.core.protocol} + */ + void sendAsyncResponse(RpcMessage rpcMessage, Channel channel, Object msg); + + /** + * register processor + * + * @param messageType {@link io.seata.core.protocol.MessageType} + * @param processor {@link RemotingProcessor} + * @param executor thread pool + */ + void registerProcessor(final int messageType, final RemotingProcessor processor, final ExecutorService executor); + +} diff --git a/core/src/main/java/io/seata/core/rpc/RemotingService.java b/core/src/main/java/io/seata/core/rpc/RemotingService.java new file mode 100644 index 0000000..b38df8a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RemotingService.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * The interface Remoting service. + * + * @author slievrly + */ +@Deprecated +public interface RemotingService { + /** + * Start. + */ + void start(); + + /** + * Shutdown. + */ + void shutdown(); + +} diff --git a/core/src/main/java/io/seata/core/rpc/RpcContext.java b/core/src/main/java/io/seata/core/rpc/RpcContext.java new file mode 100644 index 0000000..2fe3cca --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/RpcContext.java @@ -0,0 +1,333 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.Channel; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.core.rpc.netty.ChannelUtil; +import io.seata.core.rpc.netty.NettyPoolKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The type rpc context. + * + * @author slievrly + */ +public class RpcContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(RpcContext.class); + + private NettyPoolKey.TransactionRole clientRole; + + private String version; + + private String applicationId; + + private String transactionServiceGroup; + + private String clientId; + + private Channel channel; + + private Set resourceSets; + + /** + * id + */ + private ConcurrentMap clientIDHolderMap; + + /** + * tm + */ + private ConcurrentMap clientTMHolderMap; + + /** + * dbkeyRm + */ + private ConcurrentMap> clientRMHolderMap; + + /** + * Release. + */ + public void release() { + Integer clientPort = ChannelUtil.getClientPortFromChannel(channel); + if (clientIDHolderMap != null) { + clientIDHolderMap = null; + } + if (clientRole == NettyPoolKey.TransactionRole.TMROLE && clientTMHolderMap != null) { + clientTMHolderMap.remove(clientPort); + clientTMHolderMap = null; + } + if (clientRole == NettyPoolKey.TransactionRole.RMROLE && clientRMHolderMap != null) { + for (Map portMap : clientRMHolderMap.values()) { + portMap.remove(clientPort); + } + clientRMHolderMap = null; + } + if (resourceSets != null) { + resourceSets.clear(); + } + } + + /** + * Hold in client channels. + * + * @param clientTMHolderMap the client tm holder map + */ + public void holdInClientChannels(ConcurrentMap clientTMHolderMap) { + if (this.clientTMHolderMap != null) { + throw new IllegalStateException(); + } + this.clientTMHolderMap = clientTMHolderMap; + Integer clientPort = ChannelUtil.getClientPortFromChannel(channel); + this.clientTMHolderMap.put(clientPort, this); + } + + /** + * Hold in identified channels. + * + * @param clientIDHolderMap the client id holder map + */ + public void holdInIdentifiedChannels(ConcurrentMap clientIDHolderMap) { + if (this.clientIDHolderMap != null) { + throw new IllegalStateException(); + } + this.clientIDHolderMap = clientIDHolderMap; + this.clientIDHolderMap.put(channel, this); + } + + /** + * Hold in resource manager channels. + * + * @param resourceId the resource id + * @param portMap the client rm holder map + */ + public void holdInResourceManagerChannels(String resourceId, ConcurrentMap portMap) { + if (this.clientRMHolderMap == null) { + this.clientRMHolderMap = new ConcurrentHashMap<>(); + } + Integer clientPort = ChannelUtil.getClientPortFromChannel(channel); + portMap.put(clientPort, this); + this.clientRMHolderMap.put(resourceId, portMap); + } + + /** + * Hold in resource manager channels. + * + * @param resourceId the resource id + * @param clientPort the client port + */ + public void holdInResourceManagerChannels(String resourceId, Integer clientPort) { + if (this.clientRMHolderMap == null) { + this.clientRMHolderMap = new ConcurrentHashMap<>(); + } + ConcurrentMap portMap = CollectionUtils.computeIfAbsent(clientRMHolderMap, resourceId, + key -> new ConcurrentHashMap<>()); + portMap.put(clientPort, this); + } + + /** + * Gets get client rm holder map. + * + * @return the get client rm holder map + */ + public ConcurrentMap> getClientRMHolderMap() { + return clientRMHolderMap; + } + + /** + * Gets port map. + * + * @param resourceId the resource id + * @return the port map + */ + public Map getPortMap(String resourceId) { + return clientRMHolderMap.get(resourceId); + } + + /** + * Gets get client id. + * + * @return the get client id + */ + public String getClientId() { + return clientId; + } + + /** + * Gets get channel. + * + * @return the get channel + */ + public Channel getChannel() { + return channel; + } + + /** + * Sets set channel. + * + * @param channel the channel + */ + public void setChannel(Channel channel) { + this.channel = channel; + } + + /** + * Gets get application id. + * + * @return the get application id + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets set application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Gets get transaction service group. + * + * @return the get transaction service group + */ + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + /** + * Sets set transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Gets get client role. + * + * @return the get client role + */ + public NettyPoolKey.TransactionRole getClientRole() { + return clientRole; + } + + /** + * Sets set client role. + * + * @param clientRole the client role + */ + public void setClientRole(NettyPoolKey.TransactionRole clientRole) { + this.clientRole = clientRole; + } + + /** + * Gets get version. + * + * @return the get version + */ + public String getVersion() { + return version; + } + + /** + * Sets set version. + * + * @param version the version + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Gets get resource sets. + * + * @return the get resource sets + */ + public Set getResourceSets() { + return resourceSets; + } + + /** + * Sets set resource sets. + * + * @param resourceSets the resource sets + */ + public void setResourceSets(Set resourceSets) { + this.resourceSets = resourceSets; + } + + /** + * Add resource. + * + * @param resource the resource + */ + public void addResource(String resource) { + if (StringUtils.isBlank(resource)) { + return; + } + if (resourceSets == null) { + this.resourceSets = new HashSet(); + } + this.resourceSets.add(resource); + } + + /** + * Add resources. + * + * @param resources the resources + */ + public void addResources(Set resources) { + if (resources == null) { return; } + if (resourceSets == null) { + this.resourceSets = new HashSet(); + } + this.resourceSets.addAll(resources); + } + + /** + * Sets client id. + * + * @param clientId the client id + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + @Override + public String toString() { + return "RpcContext{" + + "applicationId='" + applicationId + '\'' + + ", transactionServiceGroup='" + transactionServiceGroup + '\'' + + ", clientId='" + clientId + '\'' + + ", channel=" + channel + + ", resourceSets=" + resourceSets + + '}'; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/ServerMessageListener.java b/core/src/main/java/io/seata/core/rpc/ServerMessageListener.java new file mode 100644 index 0000000..efb3c4d --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ServerMessageListener.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.RpcMessage; + +/** + * The interface Server message listener. + * + * @author slievrly + */ +@Deprecated +public interface ServerMessageListener { + + /** + * On trx message. + * + * @param request the msg id + * @param ctx the ctx + */ + void onTrxMessage(RpcMessage request, ChannelHandlerContext ctx); + + /** + * On reg rm message. + * + * @param request the msg id + * @param ctx the ctx + * @param checkAuthHandler the check auth handler + */ + void onRegRmMessage(RpcMessage request, ChannelHandlerContext ctx, RegisterCheckAuthHandler checkAuthHandler); + + /** + * On reg tm message. + * + * @param request the msg id + * @param ctx the ctx + * @param checkAuthHandler the check auth handler + */ + void onRegTmMessage(RpcMessage request, ChannelHandlerContext ctx, RegisterCheckAuthHandler checkAuthHandler); + + /** + * On check message. + * + * @param request the msg id + * @param ctx the ctx + */ + void onCheckMessage(RpcMessage request, ChannelHandlerContext ctx); + +} diff --git a/core/src/main/java/io/seata/core/rpc/ServerMessageSender.java b/core/src/main/java/io/seata/core/rpc/ServerMessageSender.java new file mode 100644 index 0000000..cb04f35 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ServerMessageSender.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.netty.channel.Channel; +import io.seata.core.protocol.RpcMessage; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * The interface Server message sender. + * + * @author slievrly + */ +@Deprecated +public interface ServerMessageSender { + + /** + * Send response. + * + * @param request the request + * @param channel the channel + * @param msg the msg + */ + void sendResponse(RpcMessage request, Channel channel, Object msg); + + /** + * Sync call to RM with timeout. + * + * @param resourceId Resource ID + * @param clientId Client ID + * @param message Request message + * @param timeout timeout of the call + * @return Response message + * @throws IOException . + * @throws TimeoutException the timeout exception + */ + Object sendSyncRequest(String resourceId, String clientId, Object message, long timeout) + throws IOException, TimeoutException; + + /** + * Sync call to RM + * + * @param resourceId Resource ID + * @param clientId Client ID + * @param message Request message + * @return Response message + * @throws IOException . + * @throws TimeoutException the timeout exception + */ + Object sendSyncRequest(String resourceId, String clientId, Object message) throws IOException, TimeoutException; + + /** + * Send request with response object. + * send syn request for rm + * + * @param clientChannel the client channel + * @param message the message + * @return the object + * @throws TimeoutException the timeout exception + */ + Object sendSyncRequest(Channel clientChannel, Object message) throws TimeoutException; + + /** + * Send request with response object. + * send syn request for rm + * + * @param clientChannel the client channel + * @param message the message + * @param timeout the timeout + * @return the object + * @throws TimeoutException the timeout exception + */ + Object sendSyncRequest(Channel clientChannel, Object message, long timeout) throws TimeoutException; + + /** + * ASync call to RM + * + * @param channel channel + * @param message Request message + * @return Response message + * @throws IOException . + * @throws TimeoutException the timeout exception + */ + Object sendASyncRequest(Channel channel, Object message) throws IOException, TimeoutException; + + +} diff --git a/core/src/main/java/io/seata/core/rpc/ShutdownHook.java b/core/src/main/java/io/seata/core/rpc/ShutdownHook.java new file mode 100644 index 0000000..b38fe18 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/ShutdownHook.java @@ -0,0 +1,117 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ensure the shutdownHook is singleton + * + * @author 563868273@qq.com + */ +public class ShutdownHook extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(ShutdownHook.class); + + private static final ShutdownHook SHUTDOWN_HOOK = new ShutdownHook("ShutdownHook"); + + private final PriorityQueue disposables = new PriorityQueue<>(); + + private final AtomicBoolean destroyed = new AtomicBoolean(false); + + /** + * default 10. Lower values have higher priority + */ + private static final int DEFAULT_PRIORITY = 10; + + static { + Runtime.getRuntime().addShutdownHook(SHUTDOWN_HOOK); + } + + private ShutdownHook(String name) { + super(name); + } + + public static ShutdownHook getInstance() { + return SHUTDOWN_HOOK; + } + + public void addDisposable(Disposable disposable) { + addDisposable(disposable, DEFAULT_PRIORITY); + } + + public void addDisposable(Disposable disposable, int priority) { + disposables.add(new DisposablePriorityWrapper(disposable, priority)); + } + + @Override + public void run() { + destroyAll(); + } + + public void destroyAll() { + if (!destroyed.compareAndSet(false, true)) { + return; + } + + if (disposables.isEmpty()) { + return; + } + + LOGGER.debug("destoryAll starting"); + + while (!disposables.isEmpty()) { + Disposable disposable = disposables.poll(); + disposable.destroy(); + } + + LOGGER.debug("destoryAll finish"); + } + + /** + * for spring context + */ + public static void removeRuntimeShutdownHook() { + Runtime.getRuntime().removeShutdownHook(SHUTDOWN_HOOK); + } + + private static class DisposablePriorityWrapper implements Comparable, Disposable { + + private final Disposable disposable; + + private final int priority; + + public DisposablePriorityWrapper(Disposable disposable, int priority) { + this.disposable = disposable; + this.priority = priority; + } + + @Override + public int compareTo(DisposablePriorityWrapper challenger) { + return priority - challenger.priority; + } + + @Override + public void destroy() { + disposable.destroy(); + } + } +} + diff --git a/core/src/main/java/io/seata/core/rpc/TransactionMessageHandler.java b/core/src/main/java/io/seata/core/rpc/TransactionMessageHandler.java new file mode 100644 index 0000000..63ce251 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/TransactionMessageHandler.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.AbstractResultMessage; + +/** + * To handle the received RPC message on upper level. + * + * @author slievrly + */ +public interface TransactionMessageHandler { + + /** + * On a request received. + * + * @param request received request message + * @param context context of the RPC + * @return response to the request + */ + AbstractResultMessage onRequest(AbstractMessage request, RpcContext context); + + /** + * On a response received. + * + * @param response received response message + * @param context context of the RPC + */ + void onResponse(AbstractResultMessage response, RpcContext context); + +} diff --git a/core/src/main/java/io/seata/core/rpc/TransportProtocolType.java b/core/src/main/java/io/seata/core/rpc/TransportProtocolType.java new file mode 100644 index 0000000..6c21d1a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/TransportProtocolType.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * The enum Transport protocol type. + * + * @author slievrly + */ +public enum TransportProtocolType { + /** + * Tcp transport protocol type. + */ + TCP("tcp"), + + /** + * Unix domain socket transport protocol type. + */ + UNIX_DOMAIN_SOCKET("unix-domain-socket"); + + /** + * The Name. + */ + public final String name; + + TransportProtocolType(String name) { + this.name = name; + } + + /** + * Gets type. + * + * @param name the name + * @return the type + */ + public static TransportProtocolType getType(String name) { + name = name.trim().replace('-', '_'); + for (TransportProtocolType b : TransportProtocolType.values()) { + if (b.name().equalsIgnoreCase(name)) { + return b; + } + } + throw new IllegalArgumentException("unknown type:" + name); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/TransportServerType.java b/core/src/main/java/io/seata/core/rpc/TransportServerType.java new file mode 100644 index 0000000..6873fda --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/TransportServerType.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +/** + * The enum Transport server type. + * + * @author slievrly + */ +public enum TransportServerType { + /** + * Native transport server type. + */ + NATIVE("native"), + /** + * Nio transport server type. + */ + NIO("nio"); + + /** + * The Name. + */ + public final String name; + + TransportServerType(String name) { + this.name = name; + } + + /** + * Gets type. + * + * @param name the name + * @return the type + */ + public static TransportServerType getType(String name) { + for (TransportServerType b : TransportServerType.values()) { + if (b.name().equalsIgnoreCase(name)) { + return b; + } + } + throw new IllegalArgumentException("unknown type:" + name); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/hook/RpcHook.java b/core/src/main/java/io/seata/core/rpc/hook/RpcHook.java new file mode 100644 index 0000000..d5303a6 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/hook/RpcHook.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.hook; + +import io.seata.core.protocol.RpcMessage; + +/** + * @author ph3636 + */ +public interface RpcHook { + + void doBeforeRequest(String remoteAddr, RpcMessage request); + + void doAfterResponse(String remoteAddr, RpcMessage request, Object response); +} diff --git a/core/src/main/java/io/seata/core/rpc/hook/StatusRpcHook.java b/core/src/main/java/io/seata/core/rpc/hook/StatusRpcHook.java new file mode 100644 index 0000000..15025c7 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/hook/StatusRpcHook.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.hook; + +import io.seata.common.rpc.RpcStatus; +import io.seata.core.protocol.RpcMessage; + +/** + * @author ph3636 + */ +public class StatusRpcHook implements RpcHook { + + @Override + public void doBeforeRequest(String remoteAddr, RpcMessage request) { + RpcStatus.beginCount(remoteAddr); + } + + @Override + public void doAfterResponse(String remoteAddr, RpcMessage request, Object response) { + RpcStatus.endCount(remoteAddr); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java new file mode 100644 index 0000000..f70f953 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemoting.java @@ -0,0 +1,379 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.thread.PositiveAtomicCounter; +import io.seata.core.protocol.MessageFuture; +import io.seata.core.protocol.MessageType; +import io.seata.core.protocol.MessageTypeAware; +import io.seata.core.protocol.ProtocolConstants; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.Disposable; +import io.seata.core.rpc.hook.RpcHook; +import io.seata.core.rpc.processor.Pair; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.SocketAddress; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * The abstract netty remoting. + * + * @author slievrly + * @author zhangchenghui.dev@gmail.com + */ +public abstract class AbstractNettyRemoting implements Disposable { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNettyRemoting.class); + /** + * The Timer executor. + */ + protected final ScheduledExecutorService timerExecutor = new ScheduledThreadPoolExecutor(1, + new NamedThreadFactory("timeoutChecker", 1, true)); + /** + * The Message executor. + */ + protected final ThreadPoolExecutor messageExecutor; + + /** + * Id generator of this remoting + */ + protected final PositiveAtomicCounter idGenerator = new PositiveAtomicCounter(); + + /** + * Obtain the return result through MessageFuture blocking. + * + * @see AbstractNettyRemoting#sendSync + */ + protected final ConcurrentHashMap futures = new ConcurrentHashMap<>(); + + private static final long NOT_WRITEABLE_CHECK_MILLS = 10L; + + /** + * The Now mills. + */ + protected volatile long nowMills = 0; + private static final int TIMEOUT_CHECK_INTERNAL = 3000; + protected final Object lock = new Object(); + /** + * The Is sending. + */ + protected volatile boolean isSending = false; + private String group = "DEFAULT"; + + /** + * This container holds all processors. + * processor type {@link MessageType} + */ + protected final HashMap> processorTable = new HashMap<>(32); + + protected final List rpcHooks = EnhancedServiceLoader.loadAll(RpcHook.class); + + public void init() { + timerExecutor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + for (Map.Entry entry : futures.entrySet()) { + if (entry.getValue().isTimeout()) { + futures.remove(entry.getKey()); + entry.getValue().setResultMessage(null); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("timeout clear future: {}", entry.getValue().getRequestMessage().getBody()); + } + } + } + + nowMills = System.currentTimeMillis(); + } + }, TIMEOUT_CHECK_INTERNAL, TIMEOUT_CHECK_INTERNAL, TimeUnit.MILLISECONDS); + } + + public AbstractNettyRemoting(ThreadPoolExecutor messageExecutor) { + this.messageExecutor = messageExecutor; + } + + public int getNextMessageId() { + return idGenerator.incrementAndGet(); + } + + public ConcurrentHashMap getFutures() { + return futures; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public void destroyChannel(Channel channel) { + destroyChannel(getAddressFromChannel(channel), channel); + } + + @Override + public void destroy() { + timerExecutor.shutdown(); + messageExecutor.shutdown(); + } + + /** + * rpc sync request + * Obtain the return result through MessageFuture blocking. + * + * @param channel netty channel + * @param rpcMessage rpc message + * @param timeoutMillis rpc communication timeout + * @return response message + * @throws TimeoutException + */ + protected Object sendSync(Channel channel, RpcMessage rpcMessage, long timeoutMillis) throws TimeoutException { + if (timeoutMillis <= 0) { + throw new FrameworkException("timeout should more than 0ms"); + } + if (channel == null) { + LOGGER.warn("sendSync nothing, caused by null channel."); + return null; + } + + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(rpcMessage); + messageFuture.setTimeout(timeoutMillis); + futures.put(rpcMessage.getId(), messageFuture); + + channelWritableCheck(channel, rpcMessage.getBody()); + + String remoteAddr = ChannelUtil.getAddressFromChannel(channel); + doBeforeRpcHooks(remoteAddr, rpcMessage); + + channel.writeAndFlush(rpcMessage).addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + MessageFuture messageFuture1 = futures.remove(rpcMessage.getId()); + if (messageFuture1 != null) { + messageFuture1.setResultMessage(future.cause()); + } + destroyChannel(future.channel()); + } + }); + + try { + Object result = messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); + doAfterRpcHooks(remoteAddr, rpcMessage, result); + return result; + } catch (Exception exx) { + LOGGER.error("wait response error:{},ip:{},request:{}", exx.getMessage(), channel.remoteAddress(), + rpcMessage.getBody()); + if (exx instanceof TimeoutException) { + throw (TimeoutException) exx; + } else { + throw new RuntimeException(exx); + } + } + } + + /** + * rpc async request. + * + * @param channel netty channel + * @param rpcMessage rpc message + */ + protected void sendAsync(Channel channel, RpcMessage rpcMessage) { + channelWritableCheck(channel, rpcMessage.getBody()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("write message:" + rpcMessage.getBody() + ", channel:" + channel + ",active?" + + channel.isActive() + ",writable?" + channel.isWritable() + ",isopen?" + channel.isOpen()); + } + + doBeforeRpcHooks(ChannelUtil.getAddressFromChannel(channel), rpcMessage); + + channel.writeAndFlush(rpcMessage).addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + destroyChannel(future.channel()); + } + }); + } + + protected RpcMessage buildRequestMessage(Object msg, byte messageType) { + RpcMessage rpcMessage = new RpcMessage(); + rpcMessage.setId(getNextMessageId()); + rpcMessage.setMessageType(messageType); + rpcMessage.setCodec(ProtocolConstants.CONFIGURED_CODEC); + rpcMessage.setCompressor(ProtocolConstants.CONFIGURED_COMPRESSOR); + rpcMessage.setBody(msg); + return rpcMessage; + } + + protected RpcMessage buildResponseMessage(RpcMessage rpcMessage, Object msg, byte messageType) { + RpcMessage rpcMsg = new RpcMessage(); + rpcMsg.setMessageType(messageType); + rpcMsg.setCodec(rpcMessage.getCodec()); // same with request + rpcMsg.setCompressor(rpcMessage.getCompressor()); + rpcMsg.setBody(msg); + rpcMsg.setId(rpcMessage.getId()); + return rpcMsg; + } + + /** + * For testing. When the thread pool is full, you can change this variable and share the stack + */ + boolean allowDumpStack = false; + + /** + * Rpc message processing. + * + * @param ctx Channel handler context. + * @param rpcMessage rpc message. + * @throws Exception throws exception process message error. + * @since 1.3.0 + */ + protected void processMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s msgId:%s, body:%s", this, rpcMessage.getId(), rpcMessage.getBody())); + } + Object body = rpcMessage.getBody(); + if (body instanceof MessageTypeAware) { + MessageTypeAware messageTypeAware = (MessageTypeAware) body; + final Pair pair = this.processorTable.get((int) messageTypeAware.getTypeCode()); + if (pair != null) { + if (pair.getSecond() != null) { + try { + pair.getSecond().execute(() -> { + try { + pair.getFirst().process(ctx, rpcMessage); + } catch (Throwable th) { + LOGGER.error(FrameworkErrorCode.NetDispatch.getErrCode(), th.getMessage(), th); + } finally { + MDC.clear(); + } + }); + } catch (RejectedExecutionException e) { + LOGGER.error(FrameworkErrorCode.ThreadPoolFull.getErrCode(), + "thread pool is full, current max pool size is " + messageExecutor.getActiveCount()); + if (allowDumpStack) { + String name = ManagementFactory.getRuntimeMXBean().getName(); + String pid = name.split("@")[0]; + int idx = new Random().nextInt(100); + try { + Runtime.getRuntime().exec("jstack " + pid + " >d:/" + idx + ".log"); + } catch (IOException exx) { + LOGGER.error(exx.getMessage()); + } + allowDumpStack = false; + } + } + } else { + try { + pair.getFirst().process(ctx, rpcMessage); + } catch (Throwable th) { + LOGGER.error(FrameworkErrorCode.NetDispatch.getErrCode(), th.getMessage(), th); + } + } + } else { + LOGGER.error("This message type [{}] has no processor.", messageTypeAware.getTypeCode()); + } + } else { + LOGGER.error("This rpcMessage body[{}] is not MessageTypeAware type.", body); + } + } + + /** + * Gets address from context. + * + * @param ctx the ctx + * @return the address from context + */ + protected String getAddressFromContext(ChannelHandlerContext ctx) { + return getAddressFromChannel(ctx.channel()); + } + + /** + * Gets address from channel. + * + * @param channel the channel + * @return the address from channel + */ + protected String getAddressFromChannel(Channel channel) { + SocketAddress socketAddress = channel.remoteAddress(); + String address = socketAddress.toString(); + if (socketAddress.toString().indexOf(NettyClientConfig.getSocketAddressStartChar()) == 0) { + address = socketAddress.toString().substring(NettyClientConfig.getSocketAddressStartChar().length()); + } + return address; + } + + private void channelWritableCheck(Channel channel, Object msg) { + int tryTimes = 0; + synchronized (lock) { + while (!channel.isWritable()) { + try { + tryTimes++; + if (tryTimes > NettyClientConfig.getMaxNotWriteableRetry()) { + destroyChannel(channel); + throw new FrameworkException("msg:" + ((msg == null) ? "null" : msg.toString()), + FrameworkErrorCode.ChannelIsNotWritable); + } + lock.wait(NOT_WRITEABLE_CHECK_MILLS); + } catch (InterruptedException exx) { + LOGGER.error(exx.getMessage()); + } + } + } + } + + /** + * Destroy channel. + * + * @param serverAddress the server address + * @param channel the channel + */ + public abstract void destroyChannel(String serverAddress, Channel channel); + + protected void doBeforeRpcHooks(String remoteAddr, RpcMessage request) { + for (RpcHook rpcHook: rpcHooks) { + rpcHook.doBeforeRequest(remoteAddr, request); + } + } + + protected void doAfterRpcHooks(String remoteAddr, RpcMessage request, Object response) { + for (RpcHook rpcHook: rpcHooks) { + rpcHook.doAfterResponse(remoteAddr, request, response); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java new file mode 100644 index 0000000..1b2ce4b --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingClient.java @@ -0,0 +1,466 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import io.netty.util.concurrent.EventExecutorGroup; +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.MergeMessage; +import io.seata.core.protocol.MergedWarpMessage; +import io.seata.core.protocol.MessageFuture; +import io.seata.core.protocol.ProtocolConstants; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.AbstractGlobalEndRequest; +import io.seata.core.protocol.transaction.BranchRegisterRequest; +import io.seata.core.protocol.transaction.BranchReportRequest; +import io.seata.core.protocol.transaction.GlobalBeginRequest; +import io.seata.core.rpc.RemotingClient; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.Pair; +import io.seata.core.rpc.processor.RemotingProcessor; +import io.seata.discovery.loadbalance.LoadBalanceFactory; +import io.seata.discovery.registry.RegistryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.exception.FrameworkErrorCode.NoAvailableService; + +/** + * The netty remoting client. + * + * @author slievrly + * @author zhaojun + * @author zhangchenghui.dev@gmail.com + */ +public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting implements RemotingClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNettyRemotingClient.class); + private static final String MSG_ID_PREFIX = "msgId:"; + private static final String FUTURES_PREFIX = "futures:"; + private static final String SINGLE_LOG_POSTFIX = ";"; + private static final int MAX_MERGE_SEND_MILLS = 1; + private static final String THREAD_PREFIX_SPLIT_CHAR = "_"; + + private static final int MAX_MERGE_SEND_THREAD = 1; + private static final long KEEP_ALIVE_TIME = Integer.MAX_VALUE; + private static final long SCHEDULE_DELAY_MILLS = 60 * 1000L; + private static final long SCHEDULE_INTERVAL_MILLS = 10 * 1000L; + private static final String MERGE_THREAD_PREFIX = "rpcMergeMessageSend"; + protected final Object mergeLock = new Object(); + + /** + * When sending message type is {@link MergeMessage}, will be stored to mergeMsgMap. + */ + protected final Map mergeMsgMap = new ConcurrentHashMap<>(); + + /** + * When batch sending is enabled, the message will be stored to basketMap + * Send via asynchronous thread {@link MergedSendRunnable} + * {@link NettyClientConfig#isEnableClientBatchSendRequest} + */ + protected final ConcurrentHashMap> basketMap = new ConcurrentHashMap<>(); + + private final NettyClientBootstrap clientBootstrap; + private NettyClientChannelManager clientChannelManager; + private final NettyPoolKey.TransactionRole transactionRole; + private ExecutorService mergeSendExecutorService; + private TransactionMessageHandler transactionMessageHandler; + + @Override + public void init() { + timerExecutor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + clientChannelManager.reconnect(getTransactionServiceGroup()); + } + }, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS); + if (NettyClientConfig.isEnableClientBatchSendRequest()) { + mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD, + MAX_MERGE_SEND_THREAD, + KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD)); + mergeSendExecutorService.submit(new MergedSendRunnable()); + } + super.init(); + clientBootstrap.start(); + } + + public AbstractNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup, + ThreadPoolExecutor messageExecutor, NettyPoolKey.TransactionRole transactionRole) { + super(messageExecutor); + this.transactionRole = transactionRole; + clientBootstrap = new NettyClientBootstrap(nettyClientConfig, eventExecutorGroup, transactionRole); + clientBootstrap.setChannelHandlers(new ClientHandler()); + clientChannelManager = new NettyClientChannelManager( + new NettyPoolableFactory(this, clientBootstrap), getPoolKeyFunction(), nettyClientConfig); + } + + @Override + public Object sendSyncRequest(Object msg) throws TimeoutException { + String serverAddress = loadBalance(getTransactionServiceGroup(), msg); + int timeoutMillis = NettyClientConfig.getRpcRequestTimeout(); + RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC); + + // send batch message + // put message into basketMap, @see MergedSendRunnable + if (NettyClientConfig.isEnableClientBatchSendRequest()) { + + // send batch message is sync request, needs to create messageFuture and put it in futures. + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(rpcMessage); + messageFuture.setTimeout(timeoutMillis); + futures.put(rpcMessage.getId(), messageFuture); + + // put message into basketMap + BlockingQueue basket = CollectionUtils.computeIfAbsent(basketMap, serverAddress, + key -> new LinkedBlockingQueue<>()); + if (!basket.offer(rpcMessage)) { + LOGGER.error("put message into basketMap offer failed, serverAddress:{},rpcMessage:{}", + serverAddress, rpcMessage); + return null; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("offer message: {}", rpcMessage.getBody()); + } + if (!isSending) { + synchronized (mergeLock) { + mergeLock.notifyAll(); + } + } + + try { + return messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (Exception exx) { + LOGGER.error("wait response error:{},ip:{},request:{}", + exx.getMessage(), serverAddress, rpcMessage.getBody()); + if (exx instanceof TimeoutException) { + throw (TimeoutException) exx; + } else { + throw new RuntimeException(exx); + } + } + + } else { + Channel channel = clientChannelManager.acquireChannel(serverAddress); + return super.sendSync(channel, rpcMessage, timeoutMillis); + } + + } + + @Override + public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException { + if (channel == null) { + LOGGER.warn("sendSyncRequest nothing, caused by null channel."); + return null; + } + RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC); + return super.sendSync(channel, rpcMessage, NettyClientConfig.getRpcRequestTimeout()); + } + + @Override + public void sendAsyncRequest(Channel channel, Object msg) { + if (channel == null) { + LOGGER.warn("sendAsyncRequest nothing, caused by null channel."); + return; + } + RpcMessage rpcMessage = buildRequestMessage(msg, msg instanceof HeartbeatMessage + ? ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST + : ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY); + if (rpcMessage.getBody() instanceof MergeMessage) { + mergeMsgMap.put(rpcMessage.getId(), (MergeMessage) rpcMessage.getBody()); + } + super.sendAsync(channel, rpcMessage); + } + + @Override + public void sendAsyncResponse(String serverAddress, RpcMessage rpcMessage, Object msg) { + RpcMessage rpcMsg = buildResponseMessage(rpcMessage, msg, ProtocolConstants.MSGTYPE_RESPONSE); + Channel channel = clientChannelManager.acquireChannel(serverAddress); + super.sendAsync(channel, rpcMsg); + } + + @Override + public void registerProcessor(int requestCode, RemotingProcessor processor, ExecutorService executor) { + Pair pair = new Pair<>(processor, executor); + this.processorTable.put(requestCode, pair); + } + + @Override + public void destroyChannel(String serverAddress, Channel channel) { + clientChannelManager.destroyChannel(serverAddress, channel); + } + + @Override + public void destroy() { + clientBootstrap.shutdown(); + if (mergeSendExecutorService != null) { + mergeSendExecutorService.shutdown(); + } + super.destroy(); + } + + public void setTransactionMessageHandler(TransactionMessageHandler transactionMessageHandler) { + this.transactionMessageHandler = transactionMessageHandler; + } + + public TransactionMessageHandler getTransactionMessageHandler() { + return transactionMessageHandler; + } + + public NettyClientChannelManager getClientChannelManager() { + return clientChannelManager; + } + + private String loadBalance(String transactionServiceGroup, Object msg) { + InetSocketAddress address = null; + try { + @SuppressWarnings("unchecked") + List inetSocketAddressList = RegistryFactory.getInstance().lookup(transactionServiceGroup); + address = LoadBalanceFactory.getInstance().select(inetSocketAddressList, getXid(msg)); + } catch (Exception ex) { + LOGGER.error(ex.getMessage()); + } + if (address == null) { + throw new FrameworkException(NoAvailableService); + } + return NetUtil.toStringAddress(address); + } + + private String getXid(Object msg) { + String xid = ""; + if (msg instanceof AbstractGlobalEndRequest) { + xid = ((AbstractGlobalEndRequest) msg).getXid(); + } else if (msg instanceof GlobalBeginRequest) { + xid = ((GlobalBeginRequest) msg).getTransactionName(); + } else if (msg instanceof BranchRegisterRequest) { + xid = ((BranchRegisterRequest) msg).getXid(); + } else if (msg instanceof BranchReportRequest) { + xid = ((BranchReportRequest) msg).getXid(); + } else { + try { + Field field = msg.getClass().getDeclaredField("xid"); + xid = String.valueOf(field.get(msg)); + } catch (Exception ignore) { + } + } + return StringUtils.isBlank(xid) ? String.valueOf(ThreadLocalRandom.current().nextLong(Long.MAX_VALUE)) : xid; + } + + private String getThreadPrefix() { + return AbstractNettyRemotingClient.MERGE_THREAD_PREFIX + THREAD_PREFIX_SPLIT_CHAR + transactionRole.name(); + } + + /** + * Get pool key function. + * + * @return lambda function + */ + protected abstract Function getPoolKeyFunction(); + + /** + * Get transaction service group. + * + * @return transaction service group + */ + protected abstract String getTransactionServiceGroup(); + + /** + * The type Merged send runnable. + */ + private class MergedSendRunnable implements Runnable { + + @Override + public void run() { + while (true) { + synchronized (mergeLock) { + try { + mergeLock.wait(MAX_MERGE_SEND_MILLS); + } catch (InterruptedException e) { + } + } + isSending = true; + basketMap.forEach((address, basket) -> { + if (basket.isEmpty()) { + return; + } + + MergedWarpMessage mergeMessage = new MergedWarpMessage(); + while (!basket.isEmpty()) { + RpcMessage msg = basket.poll(); + mergeMessage.msgs.add((AbstractMessage) msg.getBody()); + mergeMessage.msgIds.add(msg.getId()); + } + if (mergeMessage.msgIds.size() > 1) { + printMergeMessageLog(mergeMessage); + } + Channel sendChannel = null; + try { + // send batch message is sync request, but there is no need to get the return value. + // Since the messageFuture has been created before the message is placed in basketMap, + // the return value will be obtained in ClientOnResponseProcessor. + sendChannel = clientChannelManager.acquireChannel(address); + AbstractNettyRemotingClient.this.sendAsyncRequest(sendChannel, mergeMessage); + } catch (FrameworkException e) { + if (e.getErrcode() == FrameworkErrorCode.ChannelIsNotWritable && sendChannel != null) { + destroyChannel(address, sendChannel); + } + // fast fail + for (Integer msgId : mergeMessage.msgIds) { + MessageFuture messageFuture = futures.remove(msgId); + if (messageFuture != null) { + messageFuture.setResultMessage(null); + } + } + LOGGER.error("client merge call failed: {}", e.getMessage(), e); + } + }); + isSending = false; + } + } + + private void printMergeMessageLog(MergedWarpMessage mergeMessage) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("merge msg size:{}", mergeMessage.msgIds.size()); + for (AbstractMessage cm : mergeMessage.msgs) { + LOGGER.debug(cm.toString()); + } + StringBuilder sb = new StringBuilder(); + for (long l : mergeMessage.msgIds) { + sb.append(MSG_ID_PREFIX).append(l).append(SINGLE_LOG_POSTFIX); + } + sb.append("\n"); + for (long l : futures.keySet()) { + sb.append(FUTURES_PREFIX).append(l).append(SINGLE_LOG_POSTFIX); + } + LOGGER.debug(sb.toString()); + } + } + } + + /** + * The type ClientHandler. + */ + @Sharable + class ClientHandler extends ChannelDuplexHandler { + + @Override + public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof RpcMessage)) { + return; + } + processMessage(ctx, (RpcMessage) msg); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) { + synchronized (lock) { + if (ctx.channel().isWritable()) { + lock.notifyAll(); + } + } + ctx.fireChannelWritabilityChanged(); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + if (messageExecutor.isShutdown()) { + return; + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info("channel inactive: {}", ctx.channel()); + } + clientChannelManager.releaseChannel(ctx.channel(), NetUtil.toStringAddress(ctx.channel().remoteAddress())); + super.channelInactive(ctx); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + if (idleStateEvent.state() == IdleState.READER_IDLE) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("channel {} read idle.", ctx.channel()); + } + try { + String serverAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + clientChannelManager.invalidateObject(serverAddress, ctx.channel()); + } catch (Exception exx) { + LOGGER.error(exx.getMessage()); + } finally { + clientChannelManager.releaseChannel(ctx.channel(), getAddressFromContext(ctx)); + } + } + if (idleStateEvent == IdleStateEvent.WRITER_IDLE_STATE_EVENT) { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("will send ping msg,channel {}", ctx.channel()); + } + AbstractNettyRemotingClient.this.sendAsyncRequest(ctx.channel(), HeartbeatMessage.PING); + } catch (Throwable throwable) { + LOGGER.error("send request error: {}", throwable.getMessage(), throwable); + } + } + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + LOGGER.error(FrameworkErrorCode.ExceptionCaught.getErrCode(), + NetUtil.toStringAddress(ctx.channel().remoteAddress()) + "connect exception. " + cause.getMessage(), cause); + clientChannelManager.releaseChannel(ctx.channel(), getAddressFromChannel(ctx.channel())); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("remove exception rm channel:{}", ctx.channel()); + } + super.exceptionCaught(ctx, cause); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(ctx + " will closed"); + } + super.close(ctx, future); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingServer.java b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingServer.java new file mode 100644 index 0000000..8a26d37 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/AbstractNettyRemotingServer.java @@ -0,0 +1,273 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.ProtocolConstants; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.RemotingServer; +import io.seata.core.rpc.RpcContext; +import io.seata.core.rpc.processor.Pair; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; + +/** + * The type abstract remoting server. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public abstract class AbstractNettyRemotingServer extends AbstractNettyRemoting implements RemotingServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNettyRemotingServer.class); + + private final NettyServerBootstrap serverBootstrap; + + @Override + public void init() { + super.init(); + serverBootstrap.start(); + } + + public AbstractNettyRemotingServer(ThreadPoolExecutor messageExecutor, NettyServerConfig nettyServerConfig) { + super(messageExecutor); + serverBootstrap = new NettyServerBootstrap(nettyServerConfig); + serverBootstrap.setChannelHandlers(new ServerHandler()); + } + + @Override + public Object sendSyncRequest(String resourceId, String clientId, Object msg) throws TimeoutException { + Channel channel = ChannelManager.getChannel(resourceId, clientId); + if (channel == null) { + throw new RuntimeException("rm client is not connected. dbkey:" + resourceId + ",clientId:" + clientId); + } + RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC); + return super.sendSync(channel, rpcMessage, NettyServerConfig.getRpcRequestTimeout()); + } + + @Override + public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException { + if (channel == null) { + throw new RuntimeException("client is not connected"); + } + RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC); + return super.sendSync(channel, rpcMessage, NettyServerConfig.getRpcRequestTimeout()); + } + + @Override + public void sendAsyncRequest(Channel channel, Object msg) { + if (channel == null) { + throw new RuntimeException("client is not connected"); + } + RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY); + super.sendAsync(channel, rpcMessage); + } + + @Override + public void sendAsyncResponse(RpcMessage rpcMessage, Channel channel, Object msg) { + Channel clientChannel = channel; + if (!(msg instanceof HeartbeatMessage)) { + clientChannel = ChannelManager.getSameClientChannel(channel); + } + if (clientChannel != null) { + RpcMessage rpcMsg = buildResponseMessage(rpcMessage, msg, msg instanceof HeartbeatMessage + ? ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE + : ProtocolConstants.MSGTYPE_RESPONSE); + super.sendAsync(clientChannel, rpcMsg); + } else { + throw new RuntimeException("channel is error."); + } + } + + @Override + public void registerProcessor(int messageType, RemotingProcessor processor, ExecutorService executor) { + Pair pair = new Pair<>(processor, executor); + this.processorTable.put(messageType, pair); + } + + /** + * Sets listen port. + * + * @param listenPort the listen port + */ + public void setListenPort(int listenPort) { + serverBootstrap.setListenPort(listenPort); + } + + /** + * Gets listen port. + * + * @return the listen port + */ + public int getListenPort() { + return serverBootstrap.getListenPort(); + } + + @Override + public void destroy() { + serverBootstrap.shutdown(); + super.destroy(); + } + + /** + * Debug log. + * + * @param info the info + */ + public void debugLog(String info) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(info); + } + } + + private void closeChannelHandlerContext(ChannelHandlerContext ctx) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("closeChannelHandlerContext channel:" + ctx.channel()); + } + ctx.disconnect(); + ctx.close(); + } + + /** + * The type ServerHandler. + */ + @ChannelHandler.Sharable + class ServerHandler extends ChannelDuplexHandler { + + /** + * Channel read. + * + * @param ctx the ctx + * @param msg the msg + * @throws Exception the exception + */ + @Override + public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof RpcMessage)) { + return; + } + processMessage(ctx, (RpcMessage) msg); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) { + synchronized (lock) { + if (ctx.channel().isWritable()) { + lock.notifyAll(); + } + } + ctx.fireChannelWritabilityChanged(); + } + + /** + * Channel inactive. + * + * @param ctx the ctx + * @throws Exception the exception + */ + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + debugLog("inactive:" + ctx); + if (messageExecutor.isShutdown()) { + return; + } + handleDisconnect(ctx); + super.channelInactive(ctx); + } + + private void handleDisconnect(ChannelHandlerContext ctx) { + final String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info(ipAndPort + " to server channel inactive."); + } + if (rpcContext != null && rpcContext.getClientRole() != null) { + rpcContext.release(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("remove channel:" + ctx.channel() + "context:" + rpcContext); + } + } else { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("remove unused channel:" + ctx.channel()); + } + } + } + + /** + * Exception caught. + * + * @param ctx the ctx + * @param cause the cause + * @throws Exception the exception + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("channel exx:" + cause.getMessage() + ",channel:" + ctx.channel()); + } + ChannelManager.releaseRpcContext(ctx.channel()); + super.exceptionCaught(ctx, cause); + } + + /** + * User event triggered. + * + * @param ctx the ctx + * @param evt the evt + * @throws Exception the exception + */ + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + debugLog("idle:" + evt); + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + if (idleStateEvent.state() == IdleState.READER_IDLE) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("channel:" + ctx.channel() + " read idle."); + } + handleDisconnect(ctx); + try { + closeChannelHandlerContext(ctx); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + } + } + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(ctx + " will closed"); + } + super.close(ctx, future); + } + + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/ChannelAuthHealthChecker.java b/core/src/main/java/io/seata/core/rpc/netty/ChannelAuthHealthChecker.java new file mode 100644 index 0000000..5008aaa --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/ChannelAuthHealthChecker.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoop; +import io.netty.channel.pool.ChannelHealthChecker; +import io.netty.util.concurrent.Future; + +/** + * The interface Channel auth health checker. + * + * @author slievrly + */ +@Deprecated +public interface ChannelAuthHealthChecker extends ChannelHealthChecker { + /** + * The constant ACTIVE. + */ + ChannelAuthHealthChecker ACTIVE = new ChannelAuthHealthChecker() { + @Override + public Future isHealthy(Channel channel) { + EventLoop loop = channel.eventLoop(); + return channel.isActive() ? loop.newSucceededFuture(Boolean.TRUE) : loop.newSucceededFuture(Boolean.FALSE); + } + }; +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/ChannelEventListener.java b/core/src/main/java/io/seata/core/rpc/netty/ChannelEventListener.java new file mode 100644 index 0000000..19868ef --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/ChannelEventListener.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; + +/** + * The interface Channel event listener. + * + * @author slievrly + */ +@Deprecated +public interface ChannelEventListener { + /** + * On channel connect. + * + * @param remoteAddr the remote addr + * @param channel the channel + */ + void onChannelConnect(final String remoteAddr, final Channel channel); + + /** + * On channel close. + * + * @param remoteAddr the remote addr + * @param channel the channel + */ + void onChannelClose(final String remoteAddr, final Channel channel); + + /** + * On channel exception. + * + * @param remoteAddr the remote addr + * @param channel the channel + */ + void onChannelException(final String remoteAddr, final Channel channel); + + /** + * On channel idle. + * + * @param remoteAddr the remote addr + * @param channel the channel + */ + void onChannelIdle(final String remoteAddr, final Channel channel); +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java b/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java new file mode 100644 index 0000000..1f3efa8 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/ChannelManager.java @@ -0,0 +1,460 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.common.Constants; +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.core.protocol.IncompatibleVersionException; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterTMRequest; +import io.seata.core.protocol.Version; +import io.seata.core.rpc.RpcContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The type channel manager. + * + * @author slievrly + */ +public class ChannelManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelManager.class); + private static final ConcurrentMap IDENTIFIED_CHANNELS = new ConcurrentHashMap<>(); + + /** + * resourceId -> applicationId -> ip -> port -> RpcContext + */ + private static final ConcurrentMap>>> RM_CHANNELS = new ConcurrentHashMap<>(); + + /** + * ip+appname,port + */ + private static final ConcurrentMap> TM_CHANNELS + = new ConcurrentHashMap<>(); + + /** + * Is registered boolean. + * + * @param channel the channel + * @return the boolean + */ + public static boolean isRegistered(Channel channel) { + return IDENTIFIED_CHANNELS.containsKey(channel); + } + + /** + * Gets get role from channel. + * + * @param channel the channel + * @return the get role from channel + */ + public static NettyPoolKey.TransactionRole getRoleFromChannel(Channel channel) { + RpcContext context = IDENTIFIED_CHANNELS.get(channel); + if (context != null) { + return context.getClientRole(); + } + return null; + } + + /** + * Gets get context from identified. + * + * @param channel the channel + * @return the get context from identified + */ + public static RpcContext getContextFromIdentified(Channel channel) { + return IDENTIFIED_CHANNELS.get(channel); + } + + private static String buildClientId(String applicationId, Channel channel) { + return applicationId + Constants.CLIENT_ID_SPLIT_CHAR + ChannelUtil.getAddressFromChannel(channel); + } + + private static String[] readClientId(String clientId) { + return clientId.split(Constants.CLIENT_ID_SPLIT_CHAR); + } + + private static RpcContext buildChannelHolder(NettyPoolKey.TransactionRole clientRole, String version, String applicationId, + String txServiceGroup, String dbkeys, Channel channel) { + RpcContext holder = new RpcContext(); + holder.setClientRole(clientRole); + holder.setVersion(version); + holder.setClientId(buildClientId(applicationId, channel)); + holder.setApplicationId(applicationId); + holder.setTransactionServiceGroup(txServiceGroup); + holder.addResources(dbKeytoSet(dbkeys)); + holder.setChannel(channel); + return holder; + } + + /** + * Register tm channel. + * + * @param request the request + * @param channel the channel + * @throws IncompatibleVersionException the incompatible version exception + */ + public static void registerTMChannel(RegisterTMRequest request, Channel channel) + throws IncompatibleVersionException { + Version.checkVersion(request.getVersion()); + RpcContext rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.TMROLE, request.getVersion(), + request.getApplicationId(), + request.getTransactionServiceGroup(), + null, channel); + rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS); + String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR + + ChannelUtil.getClientIpFromChannel(channel); + ConcurrentMap clientIdentifiedMap = CollectionUtils.computeIfAbsent(TM_CHANNELS, + clientIdentified, key -> new ConcurrentHashMap<>()); + rpcContext.holdInClientChannels(clientIdentifiedMap); + } + + /** + * Register rm channel. + * + * @param resourceManagerRequest the resource manager request + * @param channel the channel + * @throws IncompatibleVersionException the incompatible version exception + */ + public static void registerRMChannel(RegisterRMRequest resourceManagerRequest, Channel channel) + throws IncompatibleVersionException { + Version.checkVersion(resourceManagerRequest.getVersion()); + Set dbkeySet = dbKeytoSet(resourceManagerRequest.getResourceIds()); + RpcContext rpcContext; + if (!IDENTIFIED_CHANNELS.containsKey(channel)) { + rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.RMROLE, resourceManagerRequest.getVersion(), + resourceManagerRequest.getApplicationId(), resourceManagerRequest.getTransactionServiceGroup(), + resourceManagerRequest.getResourceIds(), channel); + rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS); + } else { + rpcContext = IDENTIFIED_CHANNELS.get(channel); + rpcContext.addResources(dbkeySet); + } + if (dbkeySet == null || dbkeySet.isEmpty()) { return; } + for (String resourceId : dbkeySet) { + String clientIp; + ConcurrentMap portMap = CollectionUtils.computeIfAbsent(RM_CHANNELS, resourceId, key -> new ConcurrentHashMap<>()) + .computeIfAbsent(resourceManagerRequest.getApplicationId(), key -> new ConcurrentHashMap<>()) + .computeIfAbsent(clientIp = ChannelUtil.getClientIpFromChannel(channel), key -> new ConcurrentHashMap<>()); + + rpcContext.holdInResourceManagerChannels(resourceId, portMap); + updateChannelsResource(resourceId, clientIp, resourceManagerRequest.getApplicationId()); + } + } + + private static void updateChannelsResource(String resourceId, String clientIp, String applicationId) { + ConcurrentMap sourcePortMap = RM_CHANNELS.get(resourceId).get(applicationId).get(clientIp); + for (ConcurrentMap.Entry>>> rmChannelEntry : RM_CHANNELS.entrySet()) { + if (rmChannelEntry.getKey().equals(resourceId)) { continue; } + ConcurrentMap>> applicationIdMap = rmChannelEntry.getValue(); + if (!applicationIdMap.containsKey(applicationId)) { continue; } + ConcurrentMap> clientIpMap = applicationIdMap.get(applicationId); + if (!clientIpMap.containsKey(clientIp)) { continue; } + ConcurrentMap portMap = clientIpMap.get(clientIp); + for (ConcurrentMap.Entry portMapEntry : portMap.entrySet()) { + Integer port = portMapEntry.getKey(); + if (!sourcePortMap.containsKey(port)) { + RpcContext rpcContext = portMapEntry.getValue(); + sourcePortMap.put(port, rpcContext); + rpcContext.holdInResourceManagerChannels(resourceId, port); + } + } + } + } + + private static Set dbKeytoSet(String dbkey) { + if (StringUtils.isNullOrEmpty(dbkey)) { + return null; + } + return new HashSet(Arrays.asList(dbkey.split(Constants.DBKEYS_SPLIT_CHAR))); + } + + /** + * Release rpc context. + * + * @param channel the channel + */ + public static void releaseRpcContext(Channel channel) { + RpcContext rpcContext = getContextFromIdentified(channel); + if (rpcContext != null) { + rpcContext.release(); + } + } + + /** + * Gets get same income client channel. + * + * @param channel the channel + * @return the get same income client channel + */ + public static Channel getSameClientChannel(Channel channel) { + if (channel.isActive()) { + return channel; + } + RpcContext rpcContext = getContextFromIdentified(channel); + if (rpcContext == null) { + LOGGER.error("rpcContext is null,channel:{},active:{}", channel, channel.isActive()); + return null; + } + if (rpcContext.getChannel().isActive()) { + // recheck + return rpcContext.getChannel(); + } + Integer clientPort = ChannelUtil.getClientPortFromChannel(channel); + NettyPoolKey.TransactionRole clientRole = rpcContext.getClientRole(); + if (clientRole == NettyPoolKey.TransactionRole.TMROLE) { + String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR + + ChannelUtil.getClientIpFromChannel(channel); + if (!TM_CHANNELS.containsKey(clientIdentified)) { + return null; + } + ConcurrentMap clientRpcMap = TM_CHANNELS.get(clientIdentified); + return getChannelFromSameClientMap(clientRpcMap, clientPort); + } else if (clientRole == NettyPoolKey.TransactionRole.RMROLE) { + for (Map clientRmMap : rpcContext.getClientRMHolderMap().values()) { + Channel sameClientChannel = getChannelFromSameClientMap(clientRmMap, clientPort); + if (sameClientChannel != null) { + return sameClientChannel; + } + } + } + return null; + + } + + private static Channel getChannelFromSameClientMap(Map clientChannelMap, int exclusivePort) { + if (clientChannelMap != null && !clientChannelMap.isEmpty()) { + for (ConcurrentMap.Entry entry : clientChannelMap.entrySet()) { + if (entry.getKey() == exclusivePort) { + clientChannelMap.remove(entry.getKey()); + continue; + } + Channel channel = entry.getValue().getChannel(); + if (channel.isActive()) { return channel; } + clientChannelMap.remove(entry.getKey()); + } + } + return null; + } + + /** + * Gets get channel. + * + * @param resourceId Resource ID + * @param clientId Client ID - ApplicationId:IP:Port + * @return Corresponding channel, NULL if not found. + */ + public static Channel getChannel(String resourceId, String clientId) { + Channel resultChannel = null; + + String[] clientIdInfo = readClientId(clientId); + + if (clientIdInfo == null || clientIdInfo.length != 3) { + throw new FrameworkException("Invalid Client ID: " + clientId); + } + + String targetApplicationId = clientIdInfo[0]; + String targetIP = clientIdInfo[1]; + int targetPort = Integer.parseInt(clientIdInfo[2]); + + ConcurrentMap>> applicationIdMap = RM_CHANNELS.get(resourceId); + + if (targetApplicationId == null || applicationIdMap == null || applicationIdMap.isEmpty()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("No channel is available for resource[{}]", resourceId); + } + return null; + } + + ConcurrentMap> ipMap = applicationIdMap.get(targetApplicationId); + + if (ipMap != null && !ipMap.isEmpty()) { + // Firstly, try to find the original channel through which the branch was registered. + ConcurrentMap portMapOnTargetIP = ipMap.get(targetIP); + if (portMapOnTargetIP != null && !portMapOnTargetIP.isEmpty()) { + RpcContext exactRpcContext = portMapOnTargetIP.get(targetPort); + if (exactRpcContext != null) { + Channel channel = exactRpcContext.getChannel(); + if (channel.isActive()) { + resultChannel = channel; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Just got exactly the one {} for {}", channel, clientId); + } + } else { + if (portMapOnTargetIP.remove(targetPort, exactRpcContext)) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Removed inactive {}", channel); + } + } + } + } + + // The original channel was broken, try another one. + if (resultChannel == null) { + for (ConcurrentMap.Entry portMapOnTargetIPEntry : portMapOnTargetIP + .entrySet()) { + Channel channel = portMapOnTargetIPEntry.getValue().getChannel(); + + if (channel.isActive()) { + resultChannel = channel; + if (LOGGER.isInfoEnabled()) { + LOGGER.info( + "Choose {} on the same IP[{}] as alternative of {}", channel, targetIP, clientId); + } + break; + } else { + if (portMapOnTargetIP.remove(portMapOnTargetIPEntry.getKey(), + portMapOnTargetIPEntry.getValue())) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Removed inactive {}", channel); + } + } + } + } + } + } + + // No channel on the this app node, try another one. + if (resultChannel == null) { + for (ConcurrentMap.Entry> ipMapEntry : ipMap + .entrySet()) { + if (ipMapEntry.getKey().equals(targetIP)) { continue; } + + ConcurrentMap portMapOnOtherIP = ipMapEntry.getValue(); + if (portMapOnOtherIP == null || portMapOnOtherIP.isEmpty()) { + continue; + } + + for (ConcurrentMap.Entry portMapOnOtherIPEntry : portMapOnOtherIP.entrySet()) { + Channel channel = portMapOnOtherIPEntry.getValue().getChannel(); + + if (channel.isActive()) { + resultChannel = channel; + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Choose {} on the same application[{}] as alternative of {}", channel, targetApplicationId, clientId); + } + break; + } else { + if (portMapOnOtherIP.remove(portMapOnOtherIPEntry.getKey(), + portMapOnOtherIPEntry.getValue())) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Removed inactive {}", channel); + } + } + } + } + if (resultChannel != null) { break; } + } + } + } + + if (resultChannel == null) { + resultChannel = tryOtherApp(applicationIdMap, targetApplicationId); + + if (resultChannel == null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("No channel is available for resource[{}] as alternative of {}", resourceId, clientId); + } + } else { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Choose {} on the same resource[{}] as alternative of {}", resultChannel, resourceId, clientId); + } + } + } + + return resultChannel; + + } + + private static Channel tryOtherApp(ConcurrentMap>> applicationIdMap, String myApplicationId) { + Channel chosenChannel = null; + for (ConcurrentMap.Entry>> applicationIdMapEntry : applicationIdMap + .entrySet()) { + if (!StringUtils.isNullOrEmpty(myApplicationId) && applicationIdMapEntry.getKey().equals(myApplicationId)) { + continue; + } + + ConcurrentMap> targetIPMap = applicationIdMapEntry.getValue(); + if (targetIPMap == null || targetIPMap.isEmpty()) { + continue; + } + + for (ConcurrentMap.Entry> targetIPMapEntry : targetIPMap + .entrySet()) { + ConcurrentMap portMap = targetIPMapEntry.getValue(); + if (portMap == null || portMap.isEmpty()) { + continue; + } + + for (ConcurrentMap.Entry portMapEntry : portMap.entrySet()) { + Channel channel = portMapEntry.getValue().getChannel(); + if (channel.isActive()) { + chosenChannel = channel; + break; + } else { + if (portMap.remove(portMapEntry.getKey(), portMapEntry.getValue())) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Removed inactive {}", channel); + } + } + } + } + if (chosenChannel != null) { break; } + } + if (chosenChannel != null) { break; } + } + return chosenChannel; + + } + + /** + * get rm channels + * + * @return + */ + public static Map getRmChannels() { + if (RM_CHANNELS.isEmpty()) { + return null; + } + Map channels = new HashMap<>(RM_CHANNELS.size()); + RM_CHANNELS.forEach((resourceId, value) -> { + Channel channel = tryOtherApp(value, null); + if (channel == null) { + return; + } + channels.put(resourceId, channel); + }); + return channels; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/ChannelUtil.java b/core/src/main/java/io/seata/core/rpc/netty/ChannelUtil.java new file mode 100644 index 0000000..ca26a43 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/ChannelUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.common.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.net.SocketAddress; + +/** + * @author ph3636 + */ +public class ChannelUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChannelUtil.class); + + /** + * get address from channel + * @param channel the channel + * @return address + */ + public static String getAddressFromChannel(Channel channel) { + SocketAddress socketAddress = channel.remoteAddress(); + String address = socketAddress.toString(); + if (socketAddress.toString().indexOf(Constants.ENDPOINT_BEGIN_CHAR) == 0) { + address = socketAddress.toString().substring(Constants.ENDPOINT_BEGIN_CHAR.length()); + } + return address; + } + + /** + * get client ip from channel + * @param channel the channel + * @return client ip + */ + public static String getClientIpFromChannel(Channel channel) { + String address = getAddressFromChannel(channel); + String clientIp = address; + if (clientIp.contains(Constants.IP_PORT_SPLIT_CHAR)) { + clientIp = clientIp.substring(0, clientIp.lastIndexOf(Constants.IP_PORT_SPLIT_CHAR)); + } + return clientIp; + } + + /** + * get client port from channel + * @param channel the channel + * @return client port + */ + public static Integer getClientPortFromChannel(Channel channel) { + String address = getAddressFromChannel(channel); + Integer port = 0; + try { + if (address.contains(Constants.IP_PORT_SPLIT_CHAR)) { + port = Integer.parseInt(address.substring(address.lastIndexOf(Constants.IP_PORT_SPLIT_CHAR) + 1)); + } + } catch (NumberFormatException exx) { + LOGGER.error(exx.getMessage()); + } + return port; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyBaseConfig.java b/core/src/main/java/io/seata/core/rpc/netty/NettyBaseConfig.java new file mode 100644 index 0000000..5bf7ef9 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyBaseConfig.java @@ -0,0 +1,236 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.netty.channel.ServerChannel; +import io.netty.channel.epoll.EpollDomainSocketChannel; +import io.netty.channel.epoll.EpollServerDomainSocketChannel; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.kqueue.KQueueDomainSocketChannel; +import io.netty.channel.kqueue.KQueueServerDomainSocketChannel; +import io.netty.channel.kqueue.KQueueServerSocketChannel; +import io.netty.channel.kqueue.KQueueSocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.NettyRuntime; +import io.netty.util.internal.PlatformDependent; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.rpc.TransportProtocolType; +import io.seata.core.rpc.TransportServerType; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.DefaultValues.DEFAULT_TRANSPORT_HEARTBEAT; + +/** + * The type Netty base config. + * + * @author slievrly + */ +public class NettyBaseConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(NettyBaseConfig.class); + + /** + * The constant CONFIG. + */ + protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); + /** + * The constant BOSS_THREAD_PREFIX. + */ + protected static final String BOSS_THREAD_PREFIX = CONFIG.getConfig(ConfigurationKeys.BOSS_THREAD_PREFIX); + + /** + * The constant WORKER_THREAD_PREFIX. + */ + protected static final String WORKER_THREAD_PREFIX = CONFIG.getConfig(ConfigurationKeys.WORKER_THREAD_PREFIX); + + /** + * The constant SHARE_BOSS_WORKER. + */ + protected static final boolean SHARE_BOSS_WORKER = CONFIG.getBoolean(ConfigurationKeys.SHARE_BOSS_WORKER); + + /** + * The constant WORKER_THREAD_SIZE. + */ + protected static int WORKER_THREAD_SIZE; + + /** + * The constant TRANSPORT_SERVER_TYPE. + */ + protected static final TransportServerType TRANSPORT_SERVER_TYPE; + + /** + * The constant SERVER_CHANNEL_CLAZZ. + */ + protected static final Class SERVER_CHANNEL_CLAZZ; + /** + * The constant CLIENT_CHANNEL_CLAZZ. + */ + protected static final Class CLIENT_CHANNEL_CLAZZ; + + /** + * The constant TRANSPORT_PROTOCOL_TYPE. + */ + protected static final TransportProtocolType TRANSPORT_PROTOCOL_TYPE; + + private static final int DEFAULT_WRITE_IDLE_SECONDS = 5; + + private static final int READIDLE_BASE_WRITEIDLE = 3; + + + /** + * The constant MAX_WRITE_IDLE_SECONDS. + */ + protected static final int MAX_WRITE_IDLE_SECONDS; + + /** + * The constant MAX_READ_IDLE_SECONDS. + */ + protected static final int MAX_READ_IDLE_SECONDS; + + /** + * The constant MAX_ALL_IDLE_SECONDS. + */ + protected static final int MAX_ALL_IDLE_SECONDS = 0; + + static { + TRANSPORT_PROTOCOL_TYPE = TransportProtocolType.getType(CONFIG.getConfig(ConfigurationKeys.TRANSPORT_TYPE, TransportProtocolType.TCP.name())); + String workerThreadSize = CONFIG.getConfig(ConfigurationKeys.WORKER_THREAD_SIZE); + if (StringUtils.isNotBlank(workerThreadSize) && StringUtils.isNumeric(workerThreadSize)) { + WORKER_THREAD_SIZE = Integer.parseInt(workerThreadSize); + } else if (WorkThreadMode.getModeByName(workerThreadSize) != null) { + WORKER_THREAD_SIZE = WorkThreadMode.getModeByName(workerThreadSize).getValue(); + } else { + WORKER_THREAD_SIZE = WorkThreadMode.Default.getValue(); + } + TRANSPORT_SERVER_TYPE = TransportServerType.getType(CONFIG.getConfig(ConfigurationKeys.TRANSPORT_SERVER, TransportServerType.NIO.name())); + switch (TRANSPORT_SERVER_TYPE) { + case NIO: + if (TRANSPORT_PROTOCOL_TYPE == TransportProtocolType.TCP) { + SERVER_CHANNEL_CLAZZ = NioServerSocketChannel.class; + CLIENT_CHANNEL_CLAZZ = NioSocketChannel.class; + } else { + raiseUnsupportedTransportError(); + SERVER_CHANNEL_CLAZZ = null; + CLIENT_CHANNEL_CLAZZ = null; + } + break; + case NATIVE: + if (PlatformDependent.isWindows()) { + throw new IllegalArgumentException("no native supporting for Windows."); + } else if (PlatformDependent.isOsx()) { + if (TRANSPORT_PROTOCOL_TYPE == TransportProtocolType.TCP) { + SERVER_CHANNEL_CLAZZ = KQueueServerSocketChannel.class; + CLIENT_CHANNEL_CLAZZ = KQueueSocketChannel.class; + } else if (TRANSPORT_PROTOCOL_TYPE == TransportProtocolType.UNIX_DOMAIN_SOCKET) { + SERVER_CHANNEL_CLAZZ = KQueueServerDomainSocketChannel.class; + CLIENT_CHANNEL_CLAZZ = KQueueDomainSocketChannel.class; + } else { + raiseUnsupportedTransportError(); + SERVER_CHANNEL_CLAZZ = null; + CLIENT_CHANNEL_CLAZZ = null; + } + } else { + if (TRANSPORT_PROTOCOL_TYPE == TransportProtocolType.TCP) { + SERVER_CHANNEL_CLAZZ = EpollServerSocketChannel.class; + CLIENT_CHANNEL_CLAZZ = EpollSocketChannel.class; + } else if (TRANSPORT_PROTOCOL_TYPE == TransportProtocolType.UNIX_DOMAIN_SOCKET) { + SERVER_CHANNEL_CLAZZ = EpollServerDomainSocketChannel.class; + CLIENT_CHANNEL_CLAZZ = EpollDomainSocketChannel.class; + } else { + raiseUnsupportedTransportError(); + SERVER_CHANNEL_CLAZZ = null; + CLIENT_CHANNEL_CLAZZ = null; + } + } + break; + default: + throw new IllegalArgumentException("unsupported."); + } + boolean enableHeartbeat = CONFIG.getBoolean(ConfigurationKeys.TRANSPORT_HEARTBEAT, DEFAULT_TRANSPORT_HEARTBEAT); + if (enableHeartbeat) { + MAX_WRITE_IDLE_SECONDS = DEFAULT_WRITE_IDLE_SECONDS; + } else { + MAX_WRITE_IDLE_SECONDS = 0; + } + MAX_READ_IDLE_SECONDS = MAX_WRITE_IDLE_SECONDS * READIDLE_BASE_WRITEIDLE; + } + + private static void raiseUnsupportedTransportError() throws RuntimeException { + String errMsg = String.format("Unsupported provider type :[%s] for transport:[%s].", TRANSPORT_SERVER_TYPE, + TRANSPORT_PROTOCOL_TYPE); + LOGGER.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + + /** + * The enum Work thread mode. + */ + public enum WorkThreadMode { + + /** + * Auto work thread mode. + */ + Auto(NettyRuntime.availableProcessors() * 2 + 1), + /** + * Pin work thread mode. + */ + Pin(NettyRuntime.availableProcessors()), + /** + * Busy pin work thread mode. + */ + BusyPin(NettyRuntime.availableProcessors() + 1), + /** + * Default work thread mode. + */ + Default(NettyRuntime.availableProcessors() * 2); + + /** + * Gets value. + * + * @return the value + */ + public int getValue() { + return value; + } + + private int value; + + WorkThreadMode(int value) { + this.value = value; + } + + /** + * Gets mode by name. + * + * @param name the name + * @return the mode by name + */ + public static WorkThreadMode getModeByName(String name) { + for (WorkThreadMode mode : values()) { + if (mode.name().equalsIgnoreCase(name)) { + return mode; + } + } + return null; + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyClientBootstrap.java b/core/src/main/java/io/seata/core/rpc/netty/NettyClientBootstrap.java new file mode 100644 index 0000000..2d52bb5 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyClientBootstrap.java @@ -0,0 +1,196 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.EpollChannelOption; +import io.netty.channel.epoll.EpollMode; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.internal.PlatformDependent; +import io.seata.common.exception.FrameworkException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.core.rpc.RemotingBootstrap; +import io.seata.core.rpc.netty.v1.ProtocolV1Decoder; +import io.seata.core.rpc.netty.v1.ProtocolV1Encoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Rpc client. + * + * @author slievrly + * @author zhaojun + */ +public class NettyClientBootstrap implements RemotingBootstrap { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientBootstrap.class); + private final NettyClientConfig nettyClientConfig; + private final Bootstrap bootstrap = new Bootstrap(); + private final EventLoopGroup eventLoopGroupWorker; + private EventExecutorGroup defaultEventExecutorGroup; + private final AtomicBoolean initialized = new AtomicBoolean(false); + private static final String THREAD_PREFIX_SPLIT_CHAR = "_"; + private final NettyPoolKey.TransactionRole transactionRole; + private ChannelHandler[] channelHandlers; + + public NettyClientBootstrap(NettyClientConfig nettyClientConfig, final EventExecutorGroup eventExecutorGroup, + NettyPoolKey.TransactionRole transactionRole) { + if (nettyClientConfig == null) { + nettyClientConfig = new NettyClientConfig(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("use default netty client config."); + } + } + this.nettyClientConfig = nettyClientConfig; + int selectorThreadSizeThreadSize = this.nettyClientConfig.getClientSelectorThreadSize(); + this.transactionRole = transactionRole; + this.eventLoopGroupWorker = new NioEventLoopGroup(selectorThreadSizeThreadSize, + new NamedThreadFactory(getThreadPrefix(this.nettyClientConfig.getClientSelectorThreadPrefix()), + selectorThreadSizeThreadSize)); + this.defaultEventExecutorGroup = eventExecutorGroup; + } + + /** + * Sets channel handlers. + * + * @param handlers the handlers + */ + protected void setChannelHandlers(final ChannelHandler... handlers) { + if (handlers != null) { + channelHandlers = handlers; + } + } + + /** + * Add channel pipeline last. + * + * @param channel the channel + * @param handlers the handlers + */ + private void addChannelPipelineLast(Channel channel, ChannelHandler... handlers) { + if (channel != null && handlers != null) { + channel.pipeline().addLast(handlers); + } + } + + @Override + public void start() { + if (this.defaultEventExecutorGroup == null) { + this.defaultEventExecutorGroup = new DefaultEventExecutorGroup(nettyClientConfig.getClientWorkerThreads(), + new NamedThreadFactory(getThreadPrefix(nettyClientConfig.getClientWorkerThreadPrefix()), + nettyClientConfig.getClientWorkerThreads())); + } + this.bootstrap.group(this.eventLoopGroupWorker).channel( + nettyClientConfig.getClientChannelClazz()).option( + ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true).option( + ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis()).option( + ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()).option(ChannelOption.SO_RCVBUF, + nettyClientConfig.getClientSocketRcvBufSize()); + + if (nettyClientConfig.enableNative()) { + if (PlatformDependent.isOsx()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("client run on macOS"); + } + } else { + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.EDGE_TRIGGERED) + .option(EpollChannelOption.TCP_QUICKACK, true); + } + } + + bootstrap.handler( + new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast( + new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(), + nettyClientConfig.getChannelMaxWriteIdleSeconds(), + nettyClientConfig.getChannelMaxAllIdleSeconds())) + .addLast(new ProtocolV1Decoder()) + .addLast(new ProtocolV1Encoder()); + if (channelHandlers != null) { + addChannelPipelineLast(ch, channelHandlers); + } + } + }); + + if (initialized.compareAndSet(false, true) && LOGGER.isInfoEnabled()) { + LOGGER.info("NettyClientBootstrap has started"); + } + } + + @Override + public void shutdown() { + try { + this.eventLoopGroupWorker.shutdownGracefully(); + if (this.defaultEventExecutorGroup != null) { + this.defaultEventExecutorGroup.shutdownGracefully(); + } + } catch (Exception exx) { + LOGGER.error("Failed to shutdown: {}", exx.getMessage()); + } + } + + /** + * Gets new channel. + * + * @param address the address + * @return the new channel + */ + public Channel getNewChannel(InetSocketAddress address) { + Channel channel; + ChannelFuture f = this.bootstrap.connect(address); + try { + f.await(this.nettyClientConfig.getConnectTimeoutMillis(), TimeUnit.MILLISECONDS); + if (f.isCancelled()) { + throw new FrameworkException(f.cause(), "connect cancelled, can not connect to services-server."); + } else if (!f.isSuccess()) { + throw new FrameworkException(f.cause(), "connect failed, can not connect to services-server."); + } else { + channel = f.channel(); + } + } catch (Exception e) { + throw new FrameworkException(e, "can not connect to services-server."); + } + return channel; + } + + /** + * Gets thread prefix. + * + * @param threadPrefix the thread prefix + * @return the thread prefix + */ + private String getThreadPrefix(String threadPrefix) { + return threadPrefix + THREAD_PREFIX_SPLIT_CHAR + transactionRole.name(); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java b/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java new file mode 100644 index 0000000..3eb6827 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyClientChannelManager.java @@ -0,0 +1,267 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.discovery.registry.FileRegistryServiceImpl; +import io.seata.discovery.registry.RegistryFactory; +import io.seata.discovery.registry.RegistryService; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Netty client pool manager. + * + * @author slievrly + * @author zhaojun + */ +class NettyClientChannelManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientChannelManager.class); + + private final ConcurrentMap channelLocks = new ConcurrentHashMap<>(); + + private final ConcurrentMap poolKeyMap = new ConcurrentHashMap<>(); + + private final ConcurrentMap channels = new ConcurrentHashMap<>(); + + private final GenericKeyedObjectPool nettyClientKeyPool; + + private Function poolKeyFunction; + + NettyClientChannelManager(final NettyPoolableFactory keyPoolableFactory, final Function poolKeyFunction, + final NettyClientConfig clientConfig) { + nettyClientKeyPool = new GenericKeyedObjectPool<>(keyPoolableFactory); + nettyClientKeyPool.setConfig(getNettyPoolConfig(clientConfig)); + this.poolKeyFunction = poolKeyFunction; + } + + private GenericKeyedObjectPool.Config getNettyPoolConfig(final NettyClientConfig clientConfig) { + GenericKeyedObjectPool.Config poolConfig = new GenericKeyedObjectPool.Config(); + poolConfig.maxActive = clientConfig.getMaxPoolActive(); + poolConfig.minIdle = clientConfig.getMinPoolIdle(); + poolConfig.maxWait = clientConfig.getMaxAcquireConnMills(); + poolConfig.testOnBorrow = clientConfig.isPoolTestBorrow(); + poolConfig.testOnReturn = clientConfig.isPoolTestReturn(); + poolConfig.lifo = clientConfig.isPoolLifo(); + return poolConfig; + } + + /** + * Get all channels registered on current Rpc Client. + * + * @return channels + */ + ConcurrentMap getChannels() { + return channels; + } + + /** + * Acquire netty client channel connected to remote server. + * + * @param serverAddress server address + * @return netty channel + */ + Channel acquireChannel(String serverAddress) { + Channel channelToServer = channels.get(serverAddress); + if (channelToServer != null) { + channelToServer = getExistAliveChannel(channelToServer, serverAddress); + if (channelToServer != null) { + return channelToServer; + } + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info("will connect to " + serverAddress); + } + Object lockObj = CollectionUtils.computeIfAbsent(channelLocks, serverAddress, key -> new Object()); + synchronized (lockObj) { + return doConnect(serverAddress); + } + } + + /** + * Release channel to pool if necessary. + * + * @param channel channel + * @param serverAddress server address + */ + void releaseChannel(Channel channel, String serverAddress) { + if (channel == null || serverAddress == null) { return; } + try { + synchronized (channelLocks.get(serverAddress)) { + Channel ch = channels.get(serverAddress); + if (ch == null) { + nettyClientKeyPool.returnObject(poolKeyMap.get(serverAddress), channel); + return; + } + if (ch.compareTo(channel) == 0) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("return to pool, rm channel:{}", channel); + } + destroyChannel(serverAddress, channel); + } else { + nettyClientKeyPool.returnObject(poolKeyMap.get(serverAddress), channel); + } + } + } catch (Exception exx) { + LOGGER.error(exx.getMessage()); + } + } + + /** + * Destroy channel. + * + * @param serverAddress server address + * @param channel channel + */ + void destroyChannel(String serverAddress, Channel channel) { + if (channel == null) { return; } + try { + if (channel.equals(channels.get(serverAddress))) { + channels.remove(serverAddress); + } + nettyClientKeyPool.returnObject(poolKeyMap.get(serverAddress), channel); + } catch (Exception exx) { + LOGGER.error("return channel to rmPool error:{}", exx.getMessage()); + } + } + + /** + * Reconnect to remote server of current transaction service group. + * + * @param transactionServiceGroup transaction service group + */ + void reconnect(String transactionServiceGroup) { + List availList = null; + try { + availList = getAvailServerList(transactionServiceGroup); + } catch (Exception e) { + LOGGER.error("Failed to get available servers: {}", e.getMessage(), e); + return; + } + if (CollectionUtils.isEmpty(availList)) { + RegistryService registryService = RegistryFactory.getInstance(); + String clusterName = registryService.getServiceGroup(transactionServiceGroup); + + if (StringUtils.isBlank(clusterName)) { + LOGGER.error("can not get cluster name in registry config '{}{}', please make sure registry config correct", + ConfigurationKeys.SERVICE_GROUP_MAPPING_PREFIX, + transactionServiceGroup); + return; + } + + if (!(registryService instanceof FileRegistryServiceImpl)) { + LOGGER.error("no available service found in cluster '{}', please make sure registry config correct and keep your seata server running", clusterName); + } + return; + } + for (String serverAddress : availList) { + try { + acquireChannel(serverAddress); + } catch (Exception e) { + LOGGER.error("{} can not connect to {} cause:{}",FrameworkErrorCode.NetConnect.getErrCode(), serverAddress, e.getMessage(), e); + } + } + } + + void invalidateObject(final String serverAddress, final Channel channel) throws Exception { + nettyClientKeyPool.invalidateObject(poolKeyMap.get(serverAddress), channel); + } + + void registerChannel(final String serverAddress, final Channel channel) { + Channel channelToServer = channels.get(serverAddress); + if (channelToServer != null && channelToServer.isActive()) { + return; + } + channels.put(serverAddress, channel); + } + + private Channel doConnect(String serverAddress) { + Channel channelToServer = channels.get(serverAddress); + if (channelToServer != null && channelToServer.isActive()) { + return channelToServer; + } + Channel channelFromPool; + try { + NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress); + NettyPoolKey previousPoolKey = poolKeyMap.putIfAbsent(serverAddress, currentPoolKey); + if (previousPoolKey != null && previousPoolKey.getMessage() instanceof RegisterRMRequest) { + RegisterRMRequest registerRMRequest = (RegisterRMRequest) currentPoolKey.getMessage(); + ((RegisterRMRequest) previousPoolKey.getMessage()).setResourceIds(registerRMRequest.getResourceIds()); + } + channelFromPool = nettyClientKeyPool.borrowObject(poolKeyMap.get(serverAddress)); + channels.put(serverAddress, channelFromPool); + } catch (Exception exx) { + LOGGER.error("{} register RM failed.",FrameworkErrorCode.RegisterRM.getErrCode(), exx); + throw new FrameworkException("can not register RM,err:" + exx.getMessage()); + } + return channelFromPool; + } + + private List getAvailServerList(String transactionServiceGroup) throws Exception { + List availInetSocketAddressList = RegistryFactory.getInstance() + .lookup(transactionServiceGroup); + if (CollectionUtils.isEmpty(availInetSocketAddressList)) { + return Collections.emptyList(); + } + + return availInetSocketAddressList.stream() + .map(NetUtil::toStringAddress) + .collect(Collectors.toList()); + } + + private Channel getExistAliveChannel(Channel rmChannel, String serverAddress) { + if (rmChannel.isActive()) { + return rmChannel; + } else { + int i = 0; + for (; i < NettyClientConfig.getMaxCheckAliveRetry(); i++) { + try { + Thread.sleep(NettyClientConfig.getCheckAliveInternal()); + } catch (InterruptedException exx) { + LOGGER.error(exx.getMessage()); + } + rmChannel = channels.get(serverAddress); + if (rmChannel != null && rmChannel.isActive()) { + return rmChannel; + } + } + if (i == NettyClientConfig.getMaxCheckAliveRetry()) { + LOGGER.warn("channel {} is not active after long wait, close it.", rmChannel); + releaseChannel(rmChannel, serverAddress); + return null; + } + } + return null; + } +} + diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyClientConfig.java b/core/src/main/java/io/seata/core/rpc/netty/NettyClientConfig.java new file mode 100644 index 0000000..eea45fe --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyClientConfig.java @@ -0,0 +1,445 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.rpc.TransportServerType; + +import static io.seata.common.DefaultValues.DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST; +import static io.seata.common.DefaultValues.DEFAULT_SELECTOR_THREAD_PREFIX; +import static io.seata.common.DefaultValues.DEFAULT_SELECTOR_THREAD_SIZE; +import static io.seata.common.DefaultValues.DEFAULT_WORKER_THREAD_PREFIX; + +/** + * The type Netty client config. + * + * @author slievrly + */ +public class NettyClientConfig extends NettyBaseConfig { + + private int connectTimeoutMillis = 10000; + private int clientSocketSndBufSize = 153600; + private int clientSocketRcvBufSize = 153600; + private int clientWorkerThreads = WORKER_THREAD_SIZE; + private final Class clientChannelClazz = CLIENT_CHANNEL_CLAZZ; + private int perHostMaxConn = 2; + private static final int PER_HOST_MIN_CONN = 2; + private int pendingConnSize = Integer.MAX_VALUE; + private static final int RPC_REQUEST_TIMEOUT = 30 * 1000; + private static String vgroup; + private static String clientAppName; + private static int clientType; + private static int maxInactiveChannelCheck = 10; + private static final int MAX_NOT_WRITEABLE_RETRY = 2000; + private static final int MAX_CHECK_ALIVE_RETRY = 300; + private static final int CHECK_ALIVE_INTERNAL = 10; + private static final String SOCKET_ADDRESS_START_CHAR = "/"; + private static final long MAX_ACQUIRE_CONN_MILLS = 60 * 1000L; + private static final String RPC_DISPATCH_THREAD_PREFIX = "rpcDispatch"; + private static final int DEFAULT_MAX_POOL_ACTIVE = 1; + private static final int DEFAULT_MIN_POOL_IDLE = 0; + private static final boolean DEFAULT_POOL_TEST_BORROW = true; + private static final boolean DEFAULT_POOL_TEST_RETURN = true; + private static final boolean DEFAULT_POOL_LIFO = true; + private static final boolean ENABLE_CLIENT_BATCH_SEND_REQUEST = CONFIG.getBoolean(ConfigurationKeys.ENABLE_CLIENT_BATCH_SEND_REQUEST, DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST); + + /** + * Gets connect timeout millis. + * + * @return the connect timeout millis + */ + public int getConnectTimeoutMillis() { + return connectTimeoutMillis; + } + + /** + * Sets connect timeout millis. + * + * @param connectTimeoutMillis the connect timeout millis + */ + public void setConnectTimeoutMillis(int connectTimeoutMillis) { + this.connectTimeoutMillis = connectTimeoutMillis; + } + + /** + * Gets client socket snd buf size. + * + * @return the client socket snd buf size + */ + public int getClientSocketSndBufSize() { + return clientSocketSndBufSize; + } + + /** + * Sets client socket snd buf size. + * + * @param clientSocketSndBufSize the client socket snd buf size + */ + public void setClientSocketSndBufSize(int clientSocketSndBufSize) { + this.clientSocketSndBufSize = clientSocketSndBufSize; + } + + /** + * Gets client socket rcv buf size. + * + * @return the client socket rcv buf size + */ + public int getClientSocketRcvBufSize() { + return clientSocketRcvBufSize; + } + + /** + * Sets client socket rcv buf size. + * + * @param clientSocketRcvBufSize the client socket rcv buf size + */ + public void setClientSocketRcvBufSize(int clientSocketRcvBufSize) { + this.clientSocketRcvBufSize = clientSocketRcvBufSize; + } + + /** + * Gets client channel max idle time seconds. + * + * @return the client channel max idle time seconds + */ + public int getChannelMaxWriteIdleSeconds() { + return MAX_WRITE_IDLE_SECONDS; + } + + /** + * Gets channel max read idle seconds. + * + * @return the channel max read idle seconds + */ + public int getChannelMaxReadIdleSeconds() { + return MAX_READ_IDLE_SECONDS; + } + + /** + * Gets channel max all idle seconds. + * + * @return the channel max all idle seconds + */ + public int getChannelMaxAllIdleSeconds() { + return MAX_ALL_IDLE_SECONDS; + } + + /** + * Gets client worker threads. + * + * @return the client worker threads + */ + public int getClientWorkerThreads() { + return clientWorkerThreads; + } + + /** + * Sets client worker threads. + * + * @param clientWorkerThreads the client worker threads + */ + public void setClientWorkerThreads(int clientWorkerThreads) { + this.clientWorkerThreads = clientWorkerThreads; + } + + /** + * Gets client channel clazz. + * + * @return the client channel clazz + */ + public Class getClientChannelClazz() { + return clientChannelClazz; + } + + /** + * Enable native boolean. + * + * @return the boolean + */ + public boolean enableNative() { + return TRANSPORT_SERVER_TYPE == TransportServerType.NATIVE; + } + + /** + * Gets per host max conn. + * + * @return the per host max conn + */ + public int getPerHostMaxConn() { + return perHostMaxConn; + } + + /** + * Sets per host max conn. + * + * @param perHostMaxConn the per host max conn + */ + public void setPerHostMaxConn(int perHostMaxConn) { + if (perHostMaxConn > PER_HOST_MIN_CONN) { + this.perHostMaxConn = perHostMaxConn; + } else { + this.perHostMaxConn = PER_HOST_MIN_CONN; + } + } + + /** + * Gets pending conn size. + * + * @return the pending conn size + */ + public int getPendingConnSize() { + return pendingConnSize; + } + + /** + * Sets pending conn size. + * + * @param pendingConnSize the pending conn size + */ + public void setPendingConnSize(int pendingConnSize) { + this.pendingConnSize = pendingConnSize; + } + + /** + * Gets rpc sendAsyncRequestWithResponse time out. + * + * @return the rpc sendAsyncRequestWithResponse time out + */ + public static int getRpcRequestTimeout() { + return RPC_REQUEST_TIMEOUT; + } + + /** + * Gets vgroup. + * + * @return the vgroup + */ + public static String getVgroup() { + return vgroup; + } + + /** + * Sets vgroup. + * + * @param vgroup the vgroup + */ + public static void setVgroup(String vgroup) { + NettyClientConfig.vgroup = vgroup; + } + + /** + * Gets client app name. + * + * @return the client app name + */ + public static String getClientAppName() { + return clientAppName; + } + + /** + * Sets client app name. + * + * @param clientAppName the client app name + */ + public static void setClientAppName(String clientAppName) { + NettyClientConfig.clientAppName = clientAppName; + } + + /** + * Gets client type. + * + * @return the client type + */ + public static int getClientType() { + return clientType; + } + + /** + * Sets client type. + * + * @param clientType the client type + */ + public static void setClientType(int clientType) { + NettyClientConfig.clientType = clientType; + } + + /** + * Gets max inactive channel check. + * + * @return the max inactive channel check + */ + public static int getMaxInactiveChannelCheck() { + return maxInactiveChannelCheck; + } + + /** + * Gets max not writeable retry. + * + * @return the max not writeable retry + */ + public static int getMaxNotWriteableRetry() { + return MAX_NOT_WRITEABLE_RETRY; + } + + /** + * Gets per host min conn. + * + * @return the per host min conn + */ + public static int getPerHostMinConn() { + return PER_HOST_MIN_CONN; + } + + /** + * Gets max check alive retry. + * + * @return the max check alive retry + */ + public static int getMaxCheckAliveRetry() { + return MAX_CHECK_ALIVE_RETRY; + } + + /** + * Gets check alive internal. + * + * @return the check alive internal + */ + public static int getCheckAliveInternal() { + return CHECK_ALIVE_INTERNAL; + } + + /** + * Gets socket address start char. + * + * @return the socket address start char + */ + public static String getSocketAddressStartChar() { + return SOCKET_ADDRESS_START_CHAR; + } + + /** + * Gets client selector thread size. + * + * @return the client selector thread size + */ + public int getClientSelectorThreadSize() { + return CONFIG.getInt(ConfigurationKeys.CLIENT_SELECTOR_THREAD_SIZE, DEFAULT_SELECTOR_THREAD_SIZE); + } + + /** + * Get max acquire conn mills long. + * + * @return the long + */ + public long getMaxAcquireConnMills() { + return MAX_ACQUIRE_CONN_MILLS; + } + + /** + * Get client selector thread prefix string. + * + * @return the string + */ + public String getClientSelectorThreadPrefix() { + return CONFIG.getConfig(ConfigurationKeys.CLIENT_SELECTOR_THREAD_PREFIX, DEFAULT_SELECTOR_THREAD_PREFIX); + } + + /** + * Get client worker thread prefix string. + * + * @return the string + */ + public String getClientWorkerThreadPrefix() { + return CONFIG.getConfig(ConfigurationKeys.CLIENT_WORKER_THREAD_PREFIX, DEFAULT_WORKER_THREAD_PREFIX); + } + + /** + * Get rpc dispatch thread prefix string. + * + * @return the string + */ + public String getRpcDispatchThreadPrefix() { + return RPC_DISPATCH_THREAD_PREFIX; + } + + /** + * Gets max pool active. + * + * @return the max pool active + */ + public int getMaxPoolActive() { + return DEFAULT_MAX_POOL_ACTIVE; + } + + /** + * Gets min pool idle. + * + * @return the min pool idle + */ + public int getMinPoolIdle() { + return DEFAULT_MIN_POOL_IDLE; + } + + /** + * Is pool test borrow boolean. + * + * @return the boolean + */ + public boolean isPoolTestBorrow() { + return DEFAULT_POOL_TEST_BORROW; + } + + /** + * Is pool test return boolean. + * + * @return the boolean + */ + public boolean isPoolTestReturn() { + return DEFAULT_POOL_TEST_RETURN; + } + + /** + * Is pool fifo boolean. + * + * @return the boolean + */ + public boolean isPoolLifo() { + return DEFAULT_POOL_LIFO; + } + + /** + * Gets tm dispatch thread prefix. + * + * @return the tm dispatch thread prefix + */ + public String getTmDispatchThreadPrefix() { + return RPC_DISPATCH_THREAD_PREFIX + "_" + NettyPoolKey.TransactionRole.TMROLE.name(); + } + + /** + * Gets rm dispatch thread prefix. + * + * @return the rm dispatch thread prefix + */ + public String getRmDispatchThreadPrefix() { + return RPC_DISPATCH_THREAD_PREFIX + "_" + NettyPoolKey.TransactionRole.RMROLE.name(); + } + + public static boolean isEnableClientBatchSendRequest() { + return ENABLE_CLIENT_BATCH_SEND_REQUEST; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyPoolKey.java b/core/src/main/java/io/seata/core/rpc/netty/NettyPoolKey.java new file mode 100644 index 0000000..43d2e3e --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyPoolKey.java @@ -0,0 +1,164 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.seata.core.protocol.AbstractMessage; + +/** + * The type Netty pool key. + * + * @author slievrly + */ +public class NettyPoolKey { + + private TransactionRole transactionRole; + private String address; + private AbstractMessage message; + + /** + * Instantiates a new Netty pool key. + * + * @param transactionRole the client role + * @param address the address + */ + public NettyPoolKey(TransactionRole transactionRole, String address) { + this.transactionRole = transactionRole; + this.address = address; + } + + /** + * Instantiates a new Netty pool key. + * + * @param transactionRole the client role + * @param address the address + * @param message the message + */ + public NettyPoolKey(TransactionRole transactionRole, String address, AbstractMessage message) { + this.transactionRole = transactionRole; + this.address = address; + this.message = message; + } + + /** + * Gets get client role. + * + * @return the get client role + */ + public TransactionRole getTransactionRole() { + return transactionRole; + } + + /** + * Sets set client role. + * + * @param transactionRole the client role + * @return the client role + */ + public NettyPoolKey setTransactionRole(TransactionRole transactionRole) { + this.transactionRole = transactionRole; + return this; + } + + /** + * Gets get address. + * + * @return the get address + */ + public String getAddress() { + return address; + } + + /** + * Sets set address. + * + * @param address the address + * @return the address + */ + public NettyPoolKey setAddress(String address) { + this.address = address; + return this; + } + + /** + * Gets message. + * + * @return the message + */ + public AbstractMessage getMessage() { + return message; + } + + /** + * Sets message. + * + * @param message the message + */ + public void setMessage(AbstractMessage message) { + this.message = message; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("transactionRole:"); + sb.append(transactionRole.name()); + sb.append(","); + sb.append("address:"); + sb.append(address); + sb.append(","); + sb.append("msg:< "); + sb.append(message.toString()); + sb.append(" >"); + return sb.toString(); + } + + /** + * The enum Client role. + */ + public enum TransactionRole { + + /** + * tm + */ + TMROLE(1), + /** + * rm + */ + RMROLE(2), + /** + * server + */ + SERVERROLE(3); + + TransactionRole(int value) { + this.value = value; + } + + /** + * Gets value. + * + * @return value value + */ + public int getValue() { + return value; + } + + /** + * value + */ + private int value; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyPoolableFactory.java b/core/src/main/java/io/seata/core/rpc/netty/NettyPoolableFactory.java new file mode 100644 index 0000000..795a4e4 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyPoolableFactory.java @@ -0,0 +1,147 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import java.net.InetSocketAddress; + +import io.netty.channel.Channel; +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.protocol.RegisterTMResponse; +import org.apache.commons.pool.KeyedPoolableObjectFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Netty key poolable factory. + * + * @author slievrly + */ +public class NettyPoolableFactory implements KeyedPoolableObjectFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyPoolableFactory.class); + + private final AbstractNettyRemotingClient rpcRemotingClient; + + private final NettyClientBootstrap clientBootstrap; + + /** + * Instantiates a new Netty key poolable factory. + * + * @param rpcRemotingClient the rpc remoting client + */ + public NettyPoolableFactory(AbstractNettyRemotingClient rpcRemotingClient, NettyClientBootstrap clientBootstrap) { + this.rpcRemotingClient = rpcRemotingClient; + this.clientBootstrap = clientBootstrap; + } + + @Override + public Channel makeObject(NettyPoolKey key) { + InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress()); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("NettyPool create channel to " + key); + } + Channel tmpChannel = clientBootstrap.getNewChannel(address); + long start = System.currentTimeMillis(); + Object response; + Channel channelToServer = null; + if (key.getMessage() == null) { + throw new FrameworkException("register msg is null, role:" + key.getTransactionRole().name()); + } + try { + response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage()); + if (!isRegisterSuccess(response, key.getTransactionRole())) { + rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage()); + } else { + channelToServer = tmpChannel; + rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage()); + } + } catch (Exception exx) { + if (tmpChannel != null) { + tmpChannel.close(); + } + throw new FrameworkException( + "register " + key.getTransactionRole().name() + " error, errMsg:" + exx.getMessage()); + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info("register success, cost " + (System.currentTimeMillis() - start) + " ms, version:" + getVersion( + response, key.getTransactionRole()) + ",role:" + key.getTransactionRole().name() + ",channel:" + + channelToServer); + } + return channelToServer; + } + + private boolean isRegisterSuccess(Object response, NettyPoolKey.TransactionRole transactionRole) { + if (response == null) { + return false; + } + if (transactionRole.equals(NettyPoolKey.TransactionRole.TMROLE)) { + if (!(response instanceof RegisterTMResponse)) { + return false; + } + RegisterTMResponse registerTMResponse = (RegisterTMResponse)response; + return registerTMResponse.isIdentified(); + } else if (transactionRole.equals(NettyPoolKey.TransactionRole.RMROLE)) { + if (!(response instanceof RegisterRMResponse)) { + return false; + } + RegisterRMResponse registerRMResponse = (RegisterRMResponse)response; + return registerRMResponse.isIdentified(); + } + return false; + } + + private String getVersion(Object response, NettyPoolKey.TransactionRole transactionRole) { + if (transactionRole.equals(NettyPoolKey.TransactionRole.TMROLE)) { + return ((RegisterTMResponse) response).getVersion(); + } else { + return ((RegisterRMResponse) response).getVersion(); + } + } + + @Override + public void destroyObject(NettyPoolKey key, Channel channel) throws Exception { + if (channel != null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("will destroy channel:" + channel); + } + channel.disconnect(); + channel.close(); + } + } + + @Override + public boolean validateObject(NettyPoolKey key, Channel obj) { + if (obj != null && obj.isActive()) { + return true; + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info("channel valid false,channel:" + obj); + } + return false; + } + + @Override + public void activateObject(NettyPoolKey key, Channel obj) throws Exception { + + } + + @Override + public void passivateObject(NettyPoolKey key, Channel obj) throws Exception { + + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java b/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java new file mode 100644 index 0000000..0940c0b --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyRemotingServer.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.core.protocol.MessageType; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.server.RegRmProcessor; +import io.seata.core.rpc.processor.server.RegTmProcessor; +import io.seata.core.rpc.processor.server.ServerHeartbeatProcessor; +import io.seata.core.rpc.processor.server.ServerOnRequestProcessor; +import io.seata.core.rpc.processor.server.ServerOnResponseProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * The netty remoting server. + * + * @author slievrly + * @author xingfudeshi@gmail.com + * @author zhangchenghui.dev@gmail.com + */ +public class NettyRemotingServer extends AbstractNettyRemotingServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyRemotingServer.class); + + private TransactionMessageHandler transactionMessageHandler; + + private final AtomicBoolean initialized = new AtomicBoolean(false); + + @Override + public void init() { + // registry processor + registerProcessor(); + if (initialized.compareAndSet(false, true)) { + super.init(); + } + } + + /** + * Instantiates a new Rpc remoting server. + * + * @param messageExecutor the message executor + */ + public NettyRemotingServer(ThreadPoolExecutor messageExecutor) { + super(messageExecutor, new NettyServerConfig()); + } + + /** + * Sets transactionMessageHandler. + * + * @param transactionMessageHandler the transactionMessageHandler + */ + public void setHandler(TransactionMessageHandler transactionMessageHandler) { + this.transactionMessageHandler = transactionMessageHandler; + } + + public TransactionMessageHandler getHandler() { + return transactionMessageHandler; + } + + @Override + public void destroyChannel(String serverAddress, Channel channel) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("will destroy channel:{},address:{}", channel, serverAddress); + } + channel.disconnect(); + channel.close(); + } + + private void registerProcessor() { + // 1. registry on request message processor + ServerOnRequestProcessor onRequestProcessor = + new ServerOnRequestProcessor(this, getHandler()); + super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS, onRequestProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_SEATA_MERGE, onRequestProcessor, messageExecutor); + // 2. registry on response message processor + ServerOnResponseProcessor onResponseProcessor = + new ServerOnResponseProcessor(getHandler(), getFutures()); + super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT_RESULT, onResponseProcessor, messageExecutor); + super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK_RESULT, onResponseProcessor, messageExecutor); + // 3. registry rm message processor + RegRmProcessor regRmProcessor = new RegRmProcessor(this); + super.registerProcessor(MessageType.TYPE_REG_RM, regRmProcessor, messageExecutor); + // 4. registry tm message processor + RegTmProcessor regTmProcessor = new RegTmProcessor(this); + super.registerProcessor(MessageType.TYPE_REG_CLT, regTmProcessor, null); + // 5. registry heartbeat message processor + ServerHeartbeatProcessor heartbeatMessageProcessor = new ServerHeartbeatProcessor(this); + super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, heartbeatMessageProcessor, null); + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java b/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java new file mode 100644 index 0000000..f73fa28 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyServerBootstrap.java @@ -0,0 +1,181 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.WriteBufferWaterMark; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import io.seata.common.XID; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.core.rpc.RemotingBootstrap; +import io.seata.core.rpc.netty.v1.ProtocolV1Decoder; +import io.seata.core.rpc.netty.v1.ProtocolV1Encoder; +import io.seata.discovery.registry.RegistryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Rpc server bootstrap. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.1.0 + */ +public class NettyServerBootstrap implements RemotingBootstrap { + + private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerBootstrap.class); + private final ServerBootstrap serverBootstrap = new ServerBootstrap(); + private final EventLoopGroup eventLoopGroupWorker; + private final EventLoopGroup eventLoopGroupBoss; + private final NettyServerConfig nettyServerConfig; + private ChannelHandler[] channelHandlers; + private int listenPort; + private final AtomicBoolean initialized = new AtomicBoolean(false); + + public NettyServerBootstrap(NettyServerConfig nettyServerConfig) { + + this.nettyServerConfig = nettyServerConfig; + if (NettyServerConfig.enableEpoll()) { + this.eventLoopGroupBoss = new EpollEventLoopGroup(nettyServerConfig.getBossThreadSize(), + new NamedThreadFactory(nettyServerConfig.getBossThreadPrefix(), nettyServerConfig.getBossThreadSize())); + this.eventLoopGroupWorker = new EpollEventLoopGroup(nettyServerConfig.getServerWorkerThreads(), + new NamedThreadFactory(nettyServerConfig.getWorkerThreadPrefix(), + nettyServerConfig.getServerWorkerThreads())); + } else { + this.eventLoopGroupBoss = new NioEventLoopGroup(nettyServerConfig.getBossThreadSize(), + new NamedThreadFactory(nettyServerConfig.getBossThreadPrefix(), nettyServerConfig.getBossThreadSize())); + this.eventLoopGroupWorker = new NioEventLoopGroup(nettyServerConfig.getServerWorkerThreads(), + new NamedThreadFactory(nettyServerConfig.getWorkerThreadPrefix(), + nettyServerConfig.getServerWorkerThreads())); + } + } + + /** + * Sets channel handlers. + * + * @param handlers the handlers + */ + protected void setChannelHandlers(final ChannelHandler... handlers) { + if (handlers != null) { + channelHandlers = handlers; + } + } + + /** + * Add channel pipeline last. + * + * @param channel the channel + * @param handlers the handlers + */ + private void addChannelPipelineLast(Channel channel, ChannelHandler... handlers) { + if (channel != null && handlers != null) { + channel.pipeline().addLast(handlers); + } + } + + /** + * Sets listen port. + * + * @param listenPort the listen port + */ + public void setListenPort(int listenPort) { + + if (listenPort <= 0) { + throw new IllegalArgumentException("listen port: " + listenPort + " is invalid!"); + } + this.listenPort = listenPort; + } + + /** + * Gets listen port. + * + * @return the listen port + */ + public int getListenPort() { + return listenPort; + } + + @Override + public void start() { + this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupWorker) + .channel(NettyServerConfig.SERVER_CHANNEL_CLAZZ) + .option(ChannelOption.SO_BACKLOG, nettyServerConfig.getSoBackLogSize()) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.SO_SNDBUF, nettyServerConfig.getServerSocketSendBufSize()) + .childOption(ChannelOption.SO_RCVBUF, nettyServerConfig.getServerSocketResvBufSize()) + .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, + new WriteBufferWaterMark(nettyServerConfig.getWriteBufferLowWaterMark(), + nettyServerConfig.getWriteBufferHighWaterMark())) + .localAddress(new InetSocketAddress(listenPort)) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0)) + .addLast(new ProtocolV1Decoder()) + .addLast(new ProtocolV1Encoder()); + if (channelHandlers != null) { + addChannelPipelineLast(ch, channelHandlers); + } + + } + }); + + try { + ChannelFuture future = this.serverBootstrap.bind(listenPort).sync(); + LOGGER.info("Server started, listen port: {}", listenPort); + RegistryFactory.getInstance().register(new InetSocketAddress(XID.getIpAddress(), XID.getPort())); + initialized.set(true); + future.channel().closeFuture().sync(); + } catch (Exception exx) { + throw new RuntimeException(exx); + } + + } + + @Override + public void shutdown() { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Shutting server down. "); + } + if (initialized.get()) { + RegistryFactory.getInstance().unregister(new InetSocketAddress(XID.getIpAddress(), XID.getPort())); + RegistryFactory.getInstance().close(); + //wait a few seconds for server transport + TimeUnit.SECONDS.sleep(nettyServerConfig.getServerShutdownWaitTime()); + } + + this.eventLoopGroupBoss.shutdownGracefully(); + this.eventLoopGroupWorker.shutdownGracefully(); + } catch (Exception exx) { + LOGGER.error(exx.getMessage()); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/NettyServerConfig.java b/core/src/main/java/io/seata/core/rpc/netty/NettyServerConfig.java new file mode 100644 index 0000000..1449483 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/NettyServerConfig.java @@ -0,0 +1,305 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.ServerChannel; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.seata.core.constants.ConfigurationKeys; + +import static io.seata.common.DefaultValues.DEFAULT_BOSS_THREAD_PREFIX; +import static io.seata.common.DefaultValues.DEFAULT_BOSS_THREAD_SIZE; +import static io.seata.common.DefaultValues.DEFAULT_EXECUTOR_THREAD_PREFIX; +import static io.seata.common.DefaultValues.DEFAULT_NIO_WORKER_THREAD_PREFIX; +import static io.seata.common.DefaultValues.DEFAULT_SHUTDOWN_TIMEOUT_SEC; + +/** + * The type Netty server config. + * + * @author slievrly + */ +public class NettyServerConfig extends NettyBaseConfig { + + private int serverSelectorThreads = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "serverSelectorThreads", String.valueOf(WORKER_THREAD_SIZE))); + private int serverSocketSendBufSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "serverSocketSendBufSize", String.valueOf(153600))); + private int serverSocketResvBufSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "serverSocketResvBufSize", String.valueOf(153600))); + private int serverWorkerThreads = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "serverWorkerThreads", String.valueOf(WORKER_THREAD_SIZE))); + private int soBackLogSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "soBackLogSize", String.valueOf(1024))); + private int writeBufferHighWaterMark = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "writeBufferHighWaterMark", String.valueOf(67108864))); + private int writeBufferLowWaterMark = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "writeBufferLowWaterMark", String.valueOf(1048576))); + private static final int DEFAULT_LISTEN_PORT = 8091; + private static final int RPC_REQUEST_TIMEOUT = 30 * 1000; + private int serverChannelMaxIdleTimeSeconds = Integer.parseInt(System.getProperty( + ConfigurationKeys.TRANSPORT_PREFIX + "serverChannelMaxIdleTimeSeconds", String.valueOf(30))); + private static final String EPOLL_WORKER_THREAD_PREFIX = "NettyServerEPollWorker"; + private static int minServerPoolSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.MIN_SERVER_POOL_SIZE, "50")); + private static int maxServerPoolSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.MAX_SERVER_POOL_SIZE, "500")); + private static int maxTaskQueueSize = Integer.parseInt(System.getProperty( + ConfigurationKeys.MAX_TASK_QUEUE_SIZE, "20000")); + private static int keepAliveTime = Integer.parseInt(System.getProperty( + ConfigurationKeys.KEEP_ALIVE_TIME, "500")); + + /** + * The Server channel clazz. + */ + public static final Class SERVER_CHANNEL_CLAZZ = NettyBaseConfig.SERVER_CHANNEL_CLAZZ; + + + /** + * Gets server selector threads. + * + * @return the server selector threads + */ + public int getServerSelectorThreads() { + return serverSelectorThreads; + } + + /** + * Sets server selector threads. + * + * @param serverSelectorThreads the server selector threads + */ + public void setServerSelectorThreads(int serverSelectorThreads) { + this.serverSelectorThreads = serverSelectorThreads; + } + + /** + * Enable epoll boolean. + * + * @return the boolean + */ + public static boolean enableEpoll() { + return NettyBaseConfig.SERVER_CHANNEL_CLAZZ.equals(EpollServerSocketChannel.class) + && Epoll.isAvailable(); + + } + + /** + * Gets server socket send buf size. + * + * @return the server socket send buf size + */ + public int getServerSocketSendBufSize() { + return serverSocketSendBufSize; + } + + /** + * Sets server socket send buf size. + * + * @param serverSocketSendBufSize the server socket send buf size + */ + public void setServerSocketSendBufSize(int serverSocketSendBufSize) { + this.serverSocketSendBufSize = serverSocketSendBufSize; + } + + /** + * Gets server socket resv buf size. + * + * @return the server socket resv buf size + */ + public int getServerSocketResvBufSize() { + return serverSocketResvBufSize; + } + + /** + * Sets server socket resv buf size. + * + * @param serverSocketResvBufSize the server socket resv buf size + */ + public void setServerSocketResvBufSize(int serverSocketResvBufSize) { + this.serverSocketResvBufSize = serverSocketResvBufSize; + } + + /** + * Gets server worker threads. + * + * @return the server worker threads + */ + public int getServerWorkerThreads() { + return serverWorkerThreads; + } + + /** + * Sets server worker threads. + * + * @param serverWorkerThreads the server worker threads + */ + public void setServerWorkerThreads(int serverWorkerThreads) { + this.serverWorkerThreads = serverWorkerThreads; + } + + /** + * Gets so back log size. + * + * @return the so back log size + */ + public int getSoBackLogSize() { + return soBackLogSize; + } + + /** + * Sets so back log size. + * + * @param soBackLogSize the so back log size + */ + public void setSoBackLogSize(int soBackLogSize) { + this.soBackLogSize = soBackLogSize; + } + + /** + * Gets write buffer high water mark. + * + * @return the write buffer high water mark + */ + public int getWriteBufferHighWaterMark() { + return writeBufferHighWaterMark; + } + + /** + * Sets write buffer high water mark. + * + * @param writeBufferHighWaterMark the write buffer high water mark + */ + public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) { + this.writeBufferHighWaterMark = writeBufferHighWaterMark; + } + + /** + * Gets write buffer low water mark. + * + * @return the write buffer low water mark + */ + public int getWriteBufferLowWaterMark() { + return writeBufferLowWaterMark; + } + + /** + * Sets write buffer low water mark. + * + * @param writeBufferLowWaterMark the write buffer low water mark + */ + public void setWriteBufferLowWaterMark(int writeBufferLowWaterMark) { + this.writeBufferLowWaterMark = writeBufferLowWaterMark; + } + + /** + * Gets listen port. + * + * @return the listen port + */ + public int getDefaultListenPort() { + return DEFAULT_LISTEN_PORT; + } + + /** + * Gets channel max read idle seconds. + * + * @return the channel max read idle seconds + */ + public int getChannelMaxReadIdleSeconds() { + return MAX_READ_IDLE_SECONDS; + } + + /** + * Gets server channel max idle time seconds. + * + * @return the server channel max idle time seconds + */ + public int getServerChannelMaxIdleTimeSeconds() { + return serverChannelMaxIdleTimeSeconds; + } + + /** + * Gets rpc request timeout. + * + * @return the rpc request timeout + */ + public static int getRpcRequestTimeout() { + return RPC_REQUEST_TIMEOUT; + } + + /** + * Get boss thread prefix string. + * + * @return the string + */ + public String getBossThreadPrefix() { + return CONFIG.getConfig(ConfigurationKeys.BOSS_THREAD_PREFIX, DEFAULT_BOSS_THREAD_PREFIX); + } + + /** + * Get worker thread prefix string. + * + * @return the string + */ + public String getWorkerThreadPrefix() { + return CONFIG.getConfig(ConfigurationKeys.WORKER_THREAD_PREFIX, + enableEpoll() ? EPOLL_WORKER_THREAD_PREFIX : DEFAULT_NIO_WORKER_THREAD_PREFIX); + } + + /** + * Get executor thread prefix string. + * + * @return the string + */ + public String getExecutorThreadPrefix() { + return CONFIG.getConfig(ConfigurationKeys.SERVER_EXECUTOR_THREAD_PREFIX, + DEFAULT_EXECUTOR_THREAD_PREFIX); + } + + /** + * Get boss thread size int. + * + * @return the int + */ + public int getBossThreadSize() { + return CONFIG.getInt(ConfigurationKeys.BOSS_THREAD_SIZE, DEFAULT_BOSS_THREAD_SIZE); + } + + /** + * Get the timeout seconds of shutdown. + * + * @return the int + */ + public int getServerShutdownWaitTime() { + return CONFIG.getInt(ConfigurationKeys.SHUTDOWN_WAIT, DEFAULT_SHUTDOWN_TIMEOUT_SEC); + } + + public static int getMinServerPoolSize() { + return minServerPoolSize; + } + + public static int getMaxServerPoolSize() { + return maxServerPoolSize; + } + + public static int getMaxTaskQueueSize() { + return maxTaskQueueSize; + } + + public static int getKeepAliveTime() { + return keepAliveTime; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/RegisterMsgListener.java b/core/src/main/java/io/seata/core/rpc/netty/RegisterMsgListener.java new file mode 100644 index 0000000..f1fb4f1 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/RegisterMsgListener.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.seata.core.protocol.AbstractMessage; + +/** + * The interface Register msg listener. + * + * @author slievrly + */ +@Deprecated +public interface RegisterMsgListener { + + /** + * On register msg success. + * + * @param serverAddress the server address + * @param channel the channel + * @param response the response + * @param requestMessage the request message + */ + void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response, AbstractMessage requestMessage); + + /** + * On register msg fail. + * + * @param serverAddress the server address + * @param channel the channel + * @param response the response + * @param requestMessage the request message + */ + void onRegisterMsgFail(String serverAddress, Channel channel, Object response, AbstractMessage requestMessage); +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/RmNettyRemotingClient.java b/core/src/main/java/io/seata/core/rpc/netty/RmNettyRemotingClient.java new file mode 100644 index 0000000..e2b4323 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/RmNettyRemotingClient.java @@ -0,0 +1,292 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import io.netty.util.concurrent.EventExecutorGroup; +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.StringUtils; +import io.seata.core.model.Resource; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.MessageType; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.rpc.netty.NettyPoolKey.TransactionRole; +import io.seata.core.rpc.processor.client.ClientHeartbeatProcessor; +import io.seata.core.rpc.processor.client.ClientOnResponseProcessor; +import io.seata.core.rpc.processor.client.RmBranchCommitProcessor; +import io.seata.core.rpc.processor.client.RmBranchRollbackProcessor; +import io.seata.core.rpc.processor.client.RmUndoLogProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +import static io.seata.common.Constants.DBKEYS_SPLIT_CHAR; + +/** + * The Rm netty client. + * + * @author slievrly + * @author zhaojun + * @author zhangchenghui.dev@gmail.com + */ + +public final class RmNettyRemotingClient extends AbstractNettyRemotingClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmNettyRemotingClient.class); + private ResourceManager resourceManager; + private static volatile RmNettyRemotingClient instance; + private final AtomicBoolean initialized = new AtomicBoolean(false); + private static final long KEEP_ALIVE_TIME = Integer.MAX_VALUE; + private static final int MAX_QUEUE_SIZE = 20000; + private String applicationId; + private String transactionServiceGroup; + + @Override + public void init() { + // registry processor + registerProcessor(); + if (initialized.compareAndSet(false, true)) { + super.init(); + + // Found one or more resources that were registered before initialization + if (resourceManager != null + && !resourceManager.getManagedResources().isEmpty() + && StringUtils.isNotBlank(transactionServiceGroup)) { + getClientChannelManager().reconnect(transactionServiceGroup); + } + } + } + + private RmNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup, + ThreadPoolExecutor messageExecutor) { + super(nettyClientConfig, eventExecutorGroup, messageExecutor, TransactionRole.RMROLE); + } + + /** + * Gets instance. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @return the instance + */ + public static RmNettyRemotingClient getInstance(String applicationId, String transactionServiceGroup) { + RmNettyRemotingClient rmNettyRemotingClient = getInstance(); + rmNettyRemotingClient.setApplicationId(applicationId); + rmNettyRemotingClient.setTransactionServiceGroup(transactionServiceGroup); + return rmNettyRemotingClient; + } + + /** + * Gets instance. + * + * @return the instance + */ + public static RmNettyRemotingClient getInstance() { + if (instance == null) { + synchronized (RmNettyRemotingClient.class) { + if (instance == null) { + NettyClientConfig nettyClientConfig = new NettyClientConfig(); + final ThreadPoolExecutor messageExecutor = new ThreadPoolExecutor( + nettyClientConfig.getClientWorkerThreads(), nettyClientConfig.getClientWorkerThreads(), + KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), + new NamedThreadFactory(nettyClientConfig.getRmDispatchThreadPrefix(), + nettyClientConfig.getClientWorkerThreads()), new ThreadPoolExecutor.CallerRunsPolicy()); + instance = new RmNettyRemotingClient(nettyClientConfig, null, messageExecutor); + } + } + } + return instance; + } + + /** + * Sets application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Sets transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Sets resource manager. + * + * @param resourceManager the resource manager + */ + public void setResourceManager(ResourceManager resourceManager) { + this.resourceManager = resourceManager; + } + + @Override + public void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response, + AbstractMessage requestMessage) { + RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage; + RegisterRMResponse registerRMResponse = (RegisterRMResponse)response; + if (LOGGER.isInfoEnabled()) { + LOGGER.info("register RM success. client version:{}, server version:{},channel:{}", registerRMRequest.getVersion(), registerRMResponse.getVersion(), channel); + } + getClientChannelManager().registerChannel(serverAddress, channel); + String dbKey = getMergedResourceKeys(); + if (registerRMRequest.getResourceIds() != null) { + if (!registerRMRequest.getResourceIds().equals(dbKey)) { + sendRegisterMessage(serverAddress, channel, dbKey); + } + } + + } + + @Override + public void onRegisterMsgFail(String serverAddress, Channel channel, Object response, + AbstractMessage requestMessage) { + RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage; + RegisterRMResponse registerRMResponse = (RegisterRMResponse)response; + String errMsg = String.format( + "register RM failed. client version: %s,server version: %s, errorMsg: %s, " + "channel: %s", registerRMRequest.getVersion(), registerRMResponse.getVersion(), registerRMResponse.getMsg(), channel); + throw new FrameworkException(errMsg); + } + + /** + * Register new db key. + * + * @param resourceGroupId the resource group id + * @param resourceId the db key + */ + public void registerResource(String resourceGroupId, String resourceId) { + + // Resource registration cannot be performed until the RM client is initialized + if (StringUtils.isBlank(transactionServiceGroup)) { + return; + } + + if (getClientChannelManager().getChannels().isEmpty()) { + getClientChannelManager().reconnect(transactionServiceGroup); + return; + } + synchronized (getClientChannelManager().getChannels()) { + for (Map.Entry entry : getClientChannelManager().getChannels().entrySet()) { + String serverAddress = entry.getKey(); + Channel rmChannel = entry.getValue(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("will register resourceId:{}", resourceId); + } + sendRegisterMessage(serverAddress, rmChannel, resourceId); + } + } + } + + public void sendRegisterMessage(String serverAddress, Channel channel, String resourceId) { + RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup); + message.setResourceIds(resourceId); + try { + super.sendAsyncRequest(channel, message); + } catch (FrameworkException e) { + if (e.getErrcode() == FrameworkErrorCode.ChannelIsNotWritable && serverAddress != null) { + getClientChannelManager().releaseChannel(channel, serverAddress); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("remove not writable channel:{}", channel); + } + } else { + LOGGER.error("register resource failed, channel:{},resourceId:{}", channel, resourceId, e); + } + } + } + + public String getMergedResourceKeys() { + Map managedResources = resourceManager.getManagedResources(); + Set resourceIds = managedResources.keySet(); + if (!resourceIds.isEmpty()) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String resourceId : resourceIds) { + if (first) { + first = false; + } else { + sb.append(DBKEYS_SPLIT_CHAR); + } + sb.append(resourceId); + } + return sb.toString(); + } + return null; + } + + @Override + public void destroy() { + super.destroy(); + initialized.getAndSet(false); + instance = null; + } + + @Override + protected Function getPoolKeyFunction() { + return serverAddress -> { + String resourceIds = getMergedResourceKeys(); + if (resourceIds != null && LOGGER.isInfoEnabled()) { + LOGGER.info("RM will register :{}", resourceIds); + } + RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup); + message.setResourceIds(resourceIds); + return new NettyPoolKey(NettyPoolKey.TransactionRole.RMROLE, serverAddress, message); + }; + } + + @Override + protected String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + private void registerProcessor() { + // 1.registry rm client handle branch commit processor + RmBranchCommitProcessor rmBranchCommitProcessor = new RmBranchCommitProcessor(getTransactionMessageHandler(), this); + super.registerProcessor(MessageType.TYPE_BRANCH_COMMIT, rmBranchCommitProcessor, messageExecutor); + // 2.registry rm client handle branch commit processor + RmBranchRollbackProcessor rmBranchRollbackProcessor = new RmBranchRollbackProcessor(getTransactionMessageHandler(), this); + super.registerProcessor(MessageType.TYPE_BRANCH_ROLLBACK, rmBranchRollbackProcessor, messageExecutor); + // 3.registry rm handler undo log processor + RmUndoLogProcessor rmUndoLogProcessor = new RmUndoLogProcessor(getTransactionMessageHandler()); + super.registerProcessor(MessageType.TYPE_RM_DELETE_UNDOLOG, rmUndoLogProcessor, messageExecutor); + // 4.registry TC response processor + ClientOnResponseProcessor onResponseProcessor = + new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), getTransactionMessageHandler()); + super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_LOCK_QUERY_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_REG_RM_RESULT, onResponseProcessor, null); + // 5.registry heartbeat message processor + ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor(); + super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor, null); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/RpcEventLoopGroup.java b/core/src/main/java/io/seata/core/rpc/netty/RpcEventLoopGroup.java new file mode 100644 index 0000000..0ef16a8 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/RpcEventLoopGroup.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.EventLoopGroup; + +import java.util.concurrent.ThreadFactory; + +/** + * The interface Rpc event loop group. + * + * @author slievrly + */ +@Deprecated +public interface RpcEventLoopGroup { + + // EventLoopGroup WORKER_GROUP = new RpcEventLoopGroup() { + // @Override + // public EventLoopGroup createEventLoopGroup(int workThreadSize, ThreadFactory threadFactory) { + // return null; + // } + //}; + + /** + * Create event loop group event loop group. + * + * @param workThreadSize the work thread size + * @param threadFactory the thread factory + * @return the event loop group + */ + EventLoopGroup createEventLoopGroup(int workThreadSize, ThreadFactory threadFactory); +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java b/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java new file mode 100644 index 0000000..b1eacbc --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/TmNettyRemotingClient.java @@ -0,0 +1,254 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +import io.netty.channel.Channel; +import io.netty.util.concurrent.EventExecutorGroup; +import io.seata.common.exception.FrameworkException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.thread.RejectedPolicies; +import io.seata.common.util.NetUtil; +import io.seata.core.auth.AuthSigner; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.MessageType; +import io.seata.core.protocol.RegisterTMRequest; +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.rpc.processor.client.ClientHeartbeatProcessor; +import io.seata.core.rpc.processor.client.ClientOnResponseProcessor; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.core.constants.ConfigurationKeys.EXTRA_DATA_KV_CHAR; +import static io.seata.core.constants.ConfigurationKeys.EXTRA_DATA_SPLIT_CHAR; +import static io.seata.core.constants.ConfigurationKeys.SEATA_ACCESS_KEY; +import static io.seata.core.constants.ConfigurationKeys.SEATA_SECRET_KEY; + +/** + * The rm netty client. + * + * @author slievrly + * @author zhaojun + * @author zhangchenghui.dev@gmail.com + */ + +public final class TmNettyRemotingClient extends AbstractNettyRemotingClient { + private static final Logger LOGGER = LoggerFactory.getLogger(TmNettyRemotingClient.class); + private static volatile TmNettyRemotingClient instance; + private static final long KEEP_ALIVE_TIME = Integer.MAX_VALUE; + private static final int MAX_QUEUE_SIZE = 2000; + private final AtomicBoolean initialized = new AtomicBoolean(false); + private String applicationId; + private String transactionServiceGroup; + private final AuthSigner signer; + private String accessKey; + private String secretKey; + + + private TmNettyRemotingClient(NettyClientConfig nettyClientConfig, + EventExecutorGroup eventExecutorGroup, + ThreadPoolExecutor messageExecutor) { + super(nettyClientConfig, eventExecutorGroup, messageExecutor, NettyPoolKey.TransactionRole.TMROLE); + this.signer = EnhancedServiceLoader.load(AuthSigner.class); + } + + /** + * Gets instance. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @return the instance + */ + public static TmNettyRemotingClient getInstance(String applicationId, String transactionServiceGroup) { + return getInstance(applicationId, transactionServiceGroup, null, null); + } + + /** + * Gets instance. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @param accessKey the access key + * @param secretKey the secret key + * @return the instance + */ + public static TmNettyRemotingClient getInstance(String applicationId, String transactionServiceGroup, String accessKey, String secretKey) { + TmNettyRemotingClient tmRpcClient = getInstance(); + tmRpcClient.setApplicationId(applicationId); + tmRpcClient.setTransactionServiceGroup(transactionServiceGroup); + tmRpcClient.setAccessKey(accessKey); + tmRpcClient.setSecretKey(secretKey); + return tmRpcClient; + } + + /** + * Gets instance. + * + * @return the instance + */ + public static TmNettyRemotingClient getInstance() { + if (instance == null) { + synchronized (TmNettyRemotingClient.class) { + if (instance == null) { + NettyClientConfig nettyClientConfig = new NettyClientConfig(); + final ThreadPoolExecutor messageExecutor = new ThreadPoolExecutor( + nettyClientConfig.getClientWorkerThreads(), nettyClientConfig.getClientWorkerThreads(), + KEEP_ALIVE_TIME, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(MAX_QUEUE_SIZE), + new NamedThreadFactory(nettyClientConfig.getTmDispatchThreadPrefix(), + nettyClientConfig.getClientWorkerThreads()), + RejectedPolicies.runsOldestTaskPolicy()); + instance = new TmNettyRemotingClient(nettyClientConfig, null, messageExecutor); + } + } + } + return instance; + } + + /** + * Sets application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Sets transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Sets access key. + * + * @param accessKey the access key + */ + protected void setAccessKey(String accessKey) { + if (null != accessKey) { + this.accessKey = accessKey; + return; + } + this.accessKey = System.getProperty(SEATA_ACCESS_KEY); + } + + /** + * Sets secret key. + * + * @param secretKey the secret key + */ + protected void setSecretKey(String secretKey) { + if (null != secretKey) { + this.secretKey = secretKey; + return; + } + this.secretKey = System.getProperty(SEATA_SECRET_KEY); + } + + @Override + public void init() { + // registry processor + registerProcessor(); + if (initialized.compareAndSet(false, true)) { + super.init(); + } + } + + @Override + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + @Override + public void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response, + AbstractMessage requestMessage) { + RegisterTMRequest registerTMRequest = (RegisterTMRequest) requestMessage; + RegisterTMResponse registerTMResponse = (RegisterTMResponse) response; + if (LOGGER.isInfoEnabled()) { + LOGGER.info("register TM success. client version:{}, server version:{},channel:{}", registerTMRequest.getVersion(), registerTMResponse.getVersion(), channel); + } + getClientChannelManager().registerChannel(serverAddress, channel); + } + + @Override + public void onRegisterMsgFail(String serverAddress, Channel channel, Object response, + AbstractMessage requestMessage) { + RegisterTMRequest registerTMRequest = (RegisterTMRequest) requestMessage; + RegisterTMResponse registerTMResponse = (RegisterTMResponse) response; + String errMsg = String.format( + "register TM failed. client version: %s,server version: %s, errorMsg: %s, " + "channel: %s", registerTMRequest.getVersion(), registerTMResponse.getVersion(), registerTMResponse.getMsg(), channel); + throw new FrameworkException(errMsg); + } + + @Override + public void destroy() { + super.destroy(); + initialized.getAndSet(false); + instance = null; + } + + @Override + protected Function getPoolKeyFunction() { + return severAddress -> { + RegisterTMRequest message = new RegisterTMRequest(applicationId, transactionServiceGroup, getExtraData()); + return new NettyPoolKey(NettyPoolKey.TransactionRole.TMROLE, severAddress, message); + }; + } + + private void registerProcessor() { + // 1.registry TC response processor + ClientOnResponseProcessor onResponseProcessor = + new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), getTransactionMessageHandler()); + super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS_RESULT, onResponseProcessor, null); + super.registerProcessor(MessageType.TYPE_REG_CLT_RESULT, onResponseProcessor, null); + // 2.registry heartbeat message processor + ClientHeartbeatProcessor clientHeartbeatProcessor = new ClientHeartbeatProcessor(); + super.registerProcessor(MessageType.TYPE_HEARTBEAT_MSG, clientHeartbeatProcessor, null); + } + + private String getExtraData() { + String ip = NetUtil.getLocalIp(); + String timestamp = String.valueOf(System.currentTimeMillis()); + String digestSource; + if (StringUtils.isEmpty(ip)) { + digestSource = transactionServiceGroup + ",127.0.0.1," + timestamp; + } else { + digestSource = transactionServiceGroup + "," + ip + "," + timestamp; + } + String digest = signer.sign(digestSource, secretKey); + StringBuilder sb = new StringBuilder(); + sb.append(RegisterTMRequest.UDATA_AK).append(EXTRA_DATA_KV_CHAR).append(accessKey).append(EXTRA_DATA_SPLIT_CHAR); + sb.append(RegisterTMRequest.UDATA_DIGEST).append(EXTRA_DATA_KV_CHAR).append(digest).append(EXTRA_DATA_SPLIT_CHAR); + sb.append(RegisterTMRequest.UDATA_TIMESTAMP).append(EXTRA_DATA_KV_CHAR).append(timestamp).append(EXTRA_DATA_SPLIT_CHAR); + return sb.toString(); + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/v1/HeadMapSerializer.java b/core/src/main/java/io/seata/core/rpc/netty/v1/HeadMapSerializer.java new file mode 100644 index 0000000..363a50f --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/v1/HeadMapSerializer.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty.v1; + +import io.netty.buffer.ByteBuf; +import io.seata.common.Constants; +import io.seata.common.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * Common serializer of map (this generally refers to header). + * + * @author Geng Zhang + * @since 0.7.0 + */ +public class HeadMapSerializer { + + private static final HeadMapSerializer INSTANCE = new HeadMapSerializer(); + + private HeadMapSerializer() { + + } + + public static HeadMapSerializer getInstance() { + return INSTANCE; + } + + /** + * encode head map + * + * @param map header map + * @param out ByteBuf + * @return length of head map bytes + */ + public int encode(Map map, ByteBuf out) { + if (map == null || map.isEmpty() || out == null) { + return 0; + } + int start = out.writerIndex(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (key != null) { + writeString(out, key); + writeString(out, value); + } + } + return out.writerIndex() - start; + } + + /** + * decode head map + * + * @param in ByteBuf + * @param length of head map bytes + * @return header map + */ + public Map decode(ByteBuf in, int length) { + Map map = new HashMap<>(); + if (in == null || in.readableBytes() == 0 || length == 0) { + return map; + } + int tick = in.readerIndex(); + while (in.readerIndex() - tick < length) { + String key = readString(in); + String value = readString(in); + map.put(key, value); + } + + return map; + } + + /** + * Write string + * + * @param out ByteBuf + * @param str String + */ + protected void writeString(ByteBuf out, String str) { + if (str == null) { + out.writeShort(-1); + } else if (str.isEmpty()) { + out.writeShort(0); + } else { + byte[] bs = str.getBytes(Constants.DEFAULT_CHARSET); + out.writeShort(bs.length); + out.writeBytes(bs); + } + } + /** + * Read string + * + * @param in ByteBuf + * @return String + */ + protected String readString(ByteBuf in) { + int length = in.readShort(); + if (length < 0) { + return null; + } else if (length == 0) { + return StringUtils.EMPTY; + } else { + byte[] value = new byte[length]; + in.readBytes(value); + return new String(value, Constants.DEFAULT_CHARSET); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Decoder.java b/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Decoder.java new file mode 100644 index 0000000..4811673 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Decoder.java @@ -0,0 +1,148 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty.v1; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.core.serializer.Serializer; +import io.seata.core.compressor.Compressor; +import io.seata.core.compressor.CompressorFactory; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.ProtocolConstants; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.serializer.SerializerType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + *
+ * 0     1     2     3     4     5     6     7     8     9    10     11    12    13    14    15    16
+ * +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
+ * |   magic   |Proto|     Full length       |    Head   | Msg |Seria|Compr|     RequestId         |
+ * |   code    |colVer|    (head+body)      |   Length  |Type |lizer|ess  |                       |
+ * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
+ * |                                                                                               |
+ * |                                   Head Map [Optional]                                         |
+ * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
+ * |                                                                                               |
+ * |                                         body                                                  |
+ * |                                                                                               |
+ * |                                        ... ...                                                |
+ * +-----------------------------------------------------------------------------------------------+
+ * 
+ *

+ *

  • Full Length: include all data
  • + *
  • Head Length: include head data from magic code to head map.
  • + *
  • Body Length: Full Length - Head Length
  • + *

    + * https://github.com/seata/seata/issues/893 + * + * @author Geng Zhang + * @see ProtocolV1Encoder + * @since 0.7.0 + */ +public class ProtocolV1Decoder extends LengthFieldBasedFrameDecoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolV1Decoder.class); + + public ProtocolV1Decoder() { + // default is 8M + this(ProtocolConstants.MAX_FRAME_LENGTH); + } + + public ProtocolV1Decoder(int maxFrameLength) { + /* + int maxFrameLength, + int lengthFieldOffset, magic code is 2B, and version is 1B, and then FullLength. so value is 3 + int lengthFieldLength, FullLength is int(4B). so values is 4 + int lengthAdjustment, FullLength include all data and read 7 bytes before, so the left length is (FullLength-7). so values is -7 + int initialBytesToStrip we will check magic code and version self, so do not strip any bytes. so values is 0 + */ + super(maxFrameLength, 3, 4, -7, 0); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + Object decoded = super.decode(ctx, in); + if (decoded instanceof ByteBuf) { + ByteBuf frame = (ByteBuf) decoded; + try { + return decodeFrame(frame); + } catch (Exception e) { + LOGGER.error("Decode frame error!", e); + throw e; + } finally { + frame.release(); + } + } + return decoded; + } + + public Object decodeFrame(ByteBuf frame) { + byte b0 = frame.readByte(); + byte b1 = frame.readByte(); + if (ProtocolConstants.MAGIC_CODE_BYTES[0] != b0 + || ProtocolConstants.MAGIC_CODE_BYTES[1] != b1) { + throw new IllegalArgumentException("Unknown magic code: " + b0 + ", " + b1); + } + + byte version = frame.readByte(); + // TODO check version compatible here + + int fullLength = frame.readInt(); + short headLength = frame.readShort(); + byte messageType = frame.readByte(); + byte codecType = frame.readByte(); + byte compressorType = frame.readByte(); + int requestId = frame.readInt(); + + RpcMessage rpcMessage = new RpcMessage(); + rpcMessage.setCodec(codecType); + rpcMessage.setId(requestId); + rpcMessage.setCompressor(compressorType); + rpcMessage.setMessageType(messageType); + + // direct read head with zero-copy + int headMapLength = headLength - ProtocolConstants.V1_HEAD_LENGTH; + if (headMapLength > 0) { + Map map = HeadMapSerializer.getInstance().decode(frame, headMapLength); + rpcMessage.getHeadMap().putAll(map); + } + + // read body + if (messageType == ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST) { + rpcMessage.setBody(HeartbeatMessage.PING); + } else if (messageType == ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) { + rpcMessage.setBody(HeartbeatMessage.PONG); + } else { + int bodyLength = fullLength - headLength; + if (bodyLength > 0) { + byte[] bs = new byte[bodyLength]; + frame.readBytes(bs); + Compressor compressor = CompressorFactory.getCompressor(compressorType); + bs = compressor.decompress(bs); + Serializer serializer = EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(rpcMessage.getCodec()).name()); + rpcMessage.setBody(serializer.deserialize(bs)); + } + } + + return rpcMessage; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Encoder.java b/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Encoder.java new file mode 100644 index 0000000..0636138 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/netty/v1/ProtocolV1Encoder.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty.v1; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.core.serializer.Serializer; +import io.seata.core.compressor.Compressor; +import io.seata.core.compressor.CompressorFactory; +import io.seata.core.protocol.ProtocolConstants; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.serializer.SerializerType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + *
    + * 0     1     2     3     4     5     6     7     8     9    10     11    12    13    14    15    16
    + * +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
    + * |   magic   |Proto|     Full length       |    Head   | Msg |Seria|Compr|     RequestId         |
    + * |   code    |colVer|    (head+body)      |   Length  |Type |lizer|ess  |                       |
    + * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
    + * |                                                                                               |
    + * |                                   Head Map [Optional]                                         |
    + * +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
    + * |                                                                                               |
    + * |                                         body                                                  |
    + * |                                                                                               |
    + * |                                        ... ...                                                |
    + * +-----------------------------------------------------------------------------------------------+
    + * 
    + *

    + *

  • Full Length: include all data
  • + *
  • Head Length: include head data from magic code to head map.
  • + *
  • Body Length: Full Length - Head Length
  • + *

    + * https://github.com/seata/seata/issues/893 + * + * @author Geng Zhang + * @see ProtocolV1Decoder + * @since 0.7.0 + */ +public class ProtocolV1Encoder extends MessageToByteEncoder { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolV1Encoder.class); + + @Override + public void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) { + try { + if (msg instanceof RpcMessage) { + RpcMessage rpcMessage = (RpcMessage) msg; + + int fullLength = ProtocolConstants.V1_HEAD_LENGTH; + int headLength = ProtocolConstants.V1_HEAD_LENGTH; + + byte messageType = rpcMessage.getMessageType(); + out.writeBytes(ProtocolConstants.MAGIC_CODE_BYTES); + out.writeByte(ProtocolConstants.VERSION); + // full Length(4B) and head length(2B) will fix in the end. + out.writerIndex(out.writerIndex() + 6); + out.writeByte(messageType); + out.writeByte(rpcMessage.getCodec()); + out.writeByte(rpcMessage.getCompressor()); + out.writeInt(rpcMessage.getId()); + + // direct write head with zero-copy + Map headMap = rpcMessage.getHeadMap(); + if (headMap != null && !headMap.isEmpty()) { + int headMapBytesLength = HeadMapSerializer.getInstance().encode(headMap, out); + headLength += headMapBytesLength; + fullLength += headMapBytesLength; + } + + byte[] bodyBytes = null; + if (messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST + && messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) { + // heartbeat has no body + Serializer serializer = EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(rpcMessage.getCodec()).name()); + bodyBytes = serializer.serialize(rpcMessage.getBody()); + Compressor compressor = CompressorFactory.getCompressor(rpcMessage.getCompressor()); + bodyBytes = compressor.compress(bodyBytes); + fullLength += bodyBytes.length; + } + + if (bodyBytes != null) { + out.writeBytes(bodyBytes); + } + + // fix fullLength and headLength + int writeIndex = out.writerIndex(); + // skip magic code(2B) + version(1B) + out.writerIndex(writeIndex - fullLength + 3); + out.writeInt(fullLength); + out.writeShort(headLength); + out.writerIndex(writeIndex); + } else { + throw new UnsupportedOperationException("Not support this class:" + msg.getClass()); + } + } catch (Throwable e) { + LOGGER.error("Encode request error!", e); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/Pair.java b/core/src/main/java/io/seata/core/rpc/processor/Pair.java new file mode 100644 index 0000000..67396ae --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/Pair.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor; + +/** + * Currently used to associate first and second object. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public final class Pair { + private final T1 first; + private final T2 second; + + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } + + public T1 getFirst() { + return first; + } + + public T2 getSecond() { + return second; + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/RemotingProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/RemotingProcessor.java new file mode 100644 index 0000000..77bf9a8 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/RemotingProcessor.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.RpcMessage; + +/** + * The remoting processor + *

    + * Used to encapsulate remote interaction logic. + * In order to separate the processing business from netty. + * When netty starts, it will register processors to abstractNettyRemoting#processorTable. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public interface RemotingProcessor { + + /** + * Process message + * + * @param ctx Channel handler context. + * @param rpcMessage rpc message. + * @throws Exception throws exception process message error. + */ + void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception; + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/client/ClientHeartbeatProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/client/ClientHeartbeatProcessor.java new file mode 100644 index 0000000..3af164e --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/client/ClientHeartbeatProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process TC heartbeat message request(PONG) + *

    + * process message type: + * {@link HeartbeatMessage} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class ClientHeartbeatProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientHeartbeatProcessor.class); + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + if (rpcMessage.getBody() == HeartbeatMessage.PONG) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("received PONG from {}", ctx.channel().remoteAddress()); + } + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/client/ClientOnResponseProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/client/ClientOnResponseProcessor.java new file mode 100644 index 0000000..0a528f8 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/client/ClientOnResponseProcessor.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.AbstractResultMessage; +import io.seata.core.protocol.MergeMessage; +import io.seata.core.protocol.MergeResultMessage; +import io.seata.core.protocol.MergedWarpMessage; +import io.seata.core.protocol.MessageFuture; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.BranchRegisterResponse; +import io.seata.core.protocol.transaction.BranchReportResponse; +import io.seata.core.protocol.transaction.GlobalBeginResponse; +import io.seata.core.protocol.transaction.GlobalCommitResponse; +import io.seata.core.protocol.transaction.GlobalLockQueryResponse; +import io.seata.core.protocol.transaction.GlobalReportResponse; +import io.seata.core.protocol.transaction.GlobalRollbackResponse; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * process TC response message. + *

    + * process message type: + * RM: + * 1) {@link MergeResultMessage} + * 2) {@link RegisterRMResponse} + * 3) {@link BranchRegisterResponse} + * 4) {@link BranchReportResponse} + * 5) {@link GlobalLockQueryResponse} + * TM: + * 1) {@link MergeResultMessage} + * 2) {@link RegisterTMResponse} + * 3) {@link GlobalBeginResponse} + * 4) {@link GlobalCommitResponse} + * 5) {@link GlobalReportResponse} + * 6) {@link GlobalRollbackResponse} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class ClientOnResponseProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientOnResponseProcessor.class); + + /** + * The Merge msg map from io.seata.core.rpc.netty.AbstractNettyRemotingClient#mergeMsgMap. + */ + private Map mergeMsgMap; + + /** + * The Futures from io.seata.core.rpc.netty.AbstractNettyRemoting#futures + */ + private ConcurrentMap futures; + + /** + * To handle the received RPC message on upper level. + */ + private TransactionMessageHandler transactionMessageHandler; + + public ClientOnResponseProcessor(Map mergeMsgMap, + ConcurrentHashMap futures, + TransactionMessageHandler transactionMessageHandler) { + this.mergeMsgMap = mergeMsgMap; + this.futures = futures; + this.transactionMessageHandler = transactionMessageHandler; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + if (rpcMessage.getBody() instanceof MergeResultMessage) { + MergeResultMessage results = (MergeResultMessage) rpcMessage.getBody(); + MergedWarpMessage mergeMessage = (MergedWarpMessage) mergeMsgMap.remove(rpcMessage.getId()); + for (int i = 0; i < mergeMessage.msgs.size(); i++) { + int msgId = mergeMessage.msgIds.get(i); + MessageFuture future = futures.remove(msgId); + if (future == null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("msg: {} is not found in futures.", msgId); + } + } else { + future.setResultMessage(results.getMsgs()[i]); + } + } + } else { + MessageFuture messageFuture = futures.remove(rpcMessage.getId()); + if (messageFuture != null) { + messageFuture.setResultMessage(rpcMessage.getBody()); + } else { + if (rpcMessage.getBody() instanceof AbstractResultMessage) { + if (transactionMessageHandler != null) { + transactionMessageHandler.onResponse((AbstractResultMessage) rpcMessage.getBody(), null); + } + } + } + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchCommitProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchCommitProcessor.java new file mode 100644 index 0000000..6aed69a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchCommitProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.BranchCommitRequest; +import io.seata.core.protocol.transaction.BranchCommitResponse; +import io.seata.core.rpc.RemotingClient; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process TC global commit command. + *

    + * process message type: + * {@link BranchCommitRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class RmBranchCommitProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmBranchCommitProcessor.class); + + private TransactionMessageHandler handler; + + private RemotingClient remotingClient; + + public RmBranchCommitProcessor(TransactionMessageHandler handler, RemotingClient remotingClient) { + this.handler = handler; + this.remotingClient = remotingClient; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + Object msg = rpcMessage.getBody(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("rm client handle branch commit process:" + msg); + } + handleBranchCommit(rpcMessage, remoteAddress, (BranchCommitRequest) msg); + } + + private void handleBranchCommit(RpcMessage request, String serverAddress, BranchCommitRequest branchCommitRequest) { + BranchCommitResponse resultMessage; + resultMessage = (BranchCommitResponse) handler.onRequest(branchCommitRequest, null); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("branch commit result:" + resultMessage); + } + try { + this.remotingClient.sendAsyncResponse(serverAddress, request, resultMessage); + } catch (Throwable throwable) { + LOGGER.error("branch commit error: {}", throwable.getMessage(), throwable); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchRollbackProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchRollbackProcessor.java new file mode 100644 index 0000000..fd3cf65 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/client/RmBranchRollbackProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.BranchRollbackRequest; +import io.seata.core.protocol.transaction.BranchRollbackResponse; +import io.seata.core.rpc.RemotingClient; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process TC do global rollback command. + *

    + * process message type: + * {@link BranchRollbackRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class RmBranchRollbackProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmBranchRollbackProcessor.class); + + private TransactionMessageHandler handler; + + private RemotingClient remotingClient; + + public RmBranchRollbackProcessor(TransactionMessageHandler handler, RemotingClient remotingClient) { + this.handler = handler; + this.remotingClient = remotingClient; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + String remoteAddress = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + Object msg = rpcMessage.getBody(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("rm handle branch rollback process:" + msg); + } + handleBranchRollback(rpcMessage, remoteAddress, (BranchRollbackRequest) msg); + } + + private void handleBranchRollback(RpcMessage request, String serverAddress, BranchRollbackRequest branchRollbackRequest) { + BranchRollbackResponse resultMessage; + resultMessage = (BranchRollbackResponse) handler.onRequest(branchRollbackRequest, null); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("branch rollback result:" + resultMessage); + } + try { + this.remotingClient.sendAsyncResponse(serverAddress, request, resultMessage); + } catch (Throwable throwable) { + LOGGER.error("send response error: {}", throwable.getMessage(), throwable); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/client/RmUndoLogProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/client/RmUndoLogProcessor.java new file mode 100644 index 0000000..7500caf --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/client/RmUndoLogProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.client; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process TC undo log delete command. + *

    + * process message type: + * {@link UndoLogDeleteRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class RmUndoLogProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmUndoLogProcessor.class); + + private TransactionMessageHandler handler; + + public RmUndoLogProcessor(TransactionMessageHandler handler) { + this.handler = handler; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + Object msg = rpcMessage.getBody(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("rm handle undo log process:" + msg); + } + handleUndoLogDelete((UndoLogDeleteRequest) msg); + } + + private void handleUndoLogDelete(UndoLogDeleteRequest undoLogDeleteRequest) { + try { + handler.onRequest(undoLogDeleteRequest, null); + } catch (Exception e) { + LOGGER.error("Failed to delete undo log by undoLogDeleteRequest on" + undoLogDeleteRequest.getResourceId()); + } + } +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/BatchLogHandler.java b/core/src/main/java/io/seata/core/rpc/processor/server/BatchLogHandler.java new file mode 100644 index 0000000..6ba53c9 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/BatchLogHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.seata.common.thread.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * handle ServerOnRequestProcessor and ServerOnResponseProcessor log print. + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class BatchLogHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(BatchLogHandler.class); + + private static final BlockingQueue LOG_QUEUE = new LinkedBlockingQueue<>(); + + public static final BatchLogHandler INSTANCE = new BatchLogHandler(); + + private static final int MAX_LOG_SEND_THREAD = 1; + private static final int MAX_LOG_TAKE_SIZE = 1024; + private static final long KEEP_ALIVE_TIME = 0L; + private static final String THREAD_PREFIX = "batchLoggerPrint"; + private static final long BUSY_SLEEP_MILLS = 5L; + + static { + ExecutorService mergeSendExecutorService = new ThreadPoolExecutor(MAX_LOG_SEND_THREAD, MAX_LOG_SEND_THREAD, + KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory(THREAD_PREFIX, MAX_LOG_SEND_THREAD, true)); + mergeSendExecutorService.submit(new BatchLogRunnable()); + } + + public BlockingQueue getLogQueue() { + return LOG_QUEUE; + } + + /** + * The type Batch log runnable. + */ + static class BatchLogRunnable implements Runnable { + + @Override + public void run() { + List logList = new ArrayList<>(); + while (true) { + try { + logList.add(LOG_QUEUE.take()); + LOG_QUEUE.drainTo(logList, MAX_LOG_TAKE_SIZE); + if (LOGGER.isInfoEnabled()) { + for (String str : logList) { + LOGGER.info(str); + } + } + logList.clear(); + TimeUnit.MILLISECONDS.sleep(BUSY_SLEEP_MILLS); + } catch (InterruptedException exx) { + LOGGER.error("batch log busy sleep error:{}", exx.getMessage(), exx); + } + + } + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/RegRmProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/server/RegRmProcessor.java new file mode 100644 index 0000000..17f0d42 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/RegRmProcessor.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.Version; +import io.seata.core.rpc.netty.ChannelManager; +import io.seata.core.rpc.RemotingServer; +import io.seata.core.rpc.RegisterCheckAuthHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process RM client registry message. + *

    + * process message type: + * {@link RegisterRMRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class RegRmProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RegRmProcessor.class); + + private RemotingServer remotingServer; + + private RegisterCheckAuthHandler checkAuthHandler; + + public RegRmProcessor(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + this.checkAuthHandler = EnhancedServiceLoader.load(RegisterCheckAuthHandler.class); + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + onRegRmMessage(ctx, rpcMessage); + } + + private void onRegRmMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) { + RegisterRMRequest message = (RegisterRMRequest) rpcMessage.getBody(); + String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + boolean isSuccess = false; + String errorInfo = StringUtils.EMPTY; + try { + if (null == checkAuthHandler || checkAuthHandler.regResourceManagerCheckAuth(message)) { + ChannelManager.registerRMChannel(message, ctx.channel()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + isSuccess = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("checkAuth for client:{},vgroup:{},applicationId:{} is OK", ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId()); + } + } + } catch (Exception exx) { + isSuccess = false; + errorInfo = exx.getMessage(); + LOGGER.error("RM register fail, error message:{}", errorInfo); + } + RegisterRMResponse response = new RegisterRMResponse(isSuccess); + if (StringUtils.isNotEmpty(errorInfo)) { + response.setMsg(errorInfo); + } + remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("RM register success,message:{},channel:{},client version:{}", message, ctx.channel(), + message.getVersion()); + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/RegTmProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/server/RegTmProcessor.java new file mode 100644 index 0000000..04a34a7 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/RegTmProcessor.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.RegisterTMRequest; +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.Version; +import io.seata.core.rpc.netty.ChannelManager; +import io.seata.core.rpc.RemotingServer; +import io.seata.core.rpc.RegisterCheckAuthHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process TM client registry message. + *

    + * process message type: + * {@link RegisterTMRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class RegTmProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(RegTmProcessor.class); + + private RemotingServer remotingServer; + + private RegisterCheckAuthHandler checkAuthHandler; + + public RegTmProcessor(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + this.checkAuthHandler = EnhancedServiceLoader.load(RegisterCheckAuthHandler.class); + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + onRegTmMessage(ctx, rpcMessage); + } + + private void onRegTmMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) { + RegisterTMRequest message = (RegisterTMRequest) rpcMessage.getBody(); + String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + boolean isSuccess = false; + String errorInfo = StringUtils.EMPTY; + try { + if (null == checkAuthHandler || checkAuthHandler.regTransactionManagerCheckAuth(message)) { + ChannelManager.registerTMChannel(message, ctx.channel()); + Version.putChannelVersion(ctx.channel(), message.getVersion()); + isSuccess = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("checkAuth for client:{},vgroup:{},applicationId:{}", + ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId()); + } + } + } catch (Exception exx) { + isSuccess = false; + errorInfo = exx.getMessage(); + LOGGER.error("TM register fail, error message:{}", errorInfo); + } + RegisterTMResponse response = new RegisterTMResponse(isSuccess); + if (StringUtils.isNotEmpty(errorInfo)) { + response.setMsg(errorInfo); + } + remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("TM register success,message:{},channel:{},client version:{}", message, ctx.channel(), + message.getVersion()); + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/ServerHeartbeatProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/server/ServerHeartbeatProcessor.java new file mode 100644 index 0000000..c4ac853 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/ServerHeartbeatProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.core.protocol.HeartbeatMessage; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.rpc.RemotingServer; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process client heartbeat message request(PING). + *

    + * process message type: + * {@link HeartbeatMessage} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class ServerHeartbeatProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerHeartbeatProcessor.class); + + private RemotingServer remotingServer; + + public ServerHeartbeatProcessor(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + try { + remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), HeartbeatMessage.PONG); + } catch (Throwable throwable) { + LOGGER.error("send response error: {}", throwable.getMessage(), throwable); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("received PING from {}", ctx.channel().remoteAddress()); + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnRequestProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnRequestProcessor.java new file mode 100644 index 0000000..6340575 --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnRequestProcessor.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.AbstractResultMessage; +import io.seata.core.protocol.MergeResultMessage; +import io.seata.core.protocol.MergedWarpMessage; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.BranchRegisterRequest; +import io.seata.core.protocol.transaction.BranchReportRequest; +import io.seata.core.protocol.transaction.GlobalBeginRequest; +import io.seata.core.protocol.transaction.GlobalCommitRequest; +import io.seata.core.protocol.transaction.GlobalLockQueryRequest; +import io.seata.core.protocol.transaction.GlobalReportRequest; +import io.seata.core.protocol.transaction.GlobalRollbackRequest; +import io.seata.core.protocol.transaction.GlobalStatusRequest; +import io.seata.core.rpc.netty.ChannelManager; +import io.seata.core.rpc.RemotingServer; +import io.seata.core.rpc.RpcContext; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * process RM/TM client request message. + *

    + * message type: + * RM: + * 1) {@link MergedWarpMessage} + * 2) {@link BranchRegisterRequest} + * 3) {@link BranchReportRequest} + * 4) {@link GlobalLockQueryRequest} + * TM: + * 1) {@link MergedWarpMessage} + * 2) {@link GlobalBeginRequest} + * 3) {@link GlobalCommitRequest} + * 4) {@link GlobalReportRequest} + * 5) {@link GlobalRollbackRequest} + * 6) {@link GlobalStatusRequest} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class ServerOnRequestProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerOnRequestProcessor.class); + + private RemotingServer remotingServer; + + private TransactionMessageHandler transactionMessageHandler; + + public ServerOnRequestProcessor(RemotingServer remotingServer, TransactionMessageHandler transactionMessageHandler) { + this.remotingServer = remotingServer; + this.transactionMessageHandler = transactionMessageHandler; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + if (ChannelManager.isRegistered(ctx.channel())) { + onRequestMessage(ctx, rpcMessage); + } else { + try { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("closeChannelHandlerContext channel:" + ctx.channel()); + } + ctx.disconnect(); + ctx.close(); + } catch (Exception exx) { + LOGGER.error(exx.getMessage()); + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("close a unhandled connection! [%s]", ctx.channel().toString())); + } + } + } + + private void onRequestMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) { + Object message = rpcMessage.getBody(); + RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("server received:{},clientIp:{},vgroup:{}", message, + NetUtil.toIpAddress(ctx.channel().remoteAddress()), rpcContext.getTransactionServiceGroup()); + } else { + try { + BatchLogHandler.INSTANCE.getLogQueue() + .put(message + ",clientIp:" + NetUtil.toIpAddress(ctx.channel().remoteAddress()) + ",vgroup:" + + rpcContext.getTransactionServiceGroup()); + } catch (InterruptedException e) { + LOGGER.error("put message to logQueue error: {}", e.getMessage(), e); + } + } + if (!(message instanceof AbstractMessage)) { + return; + } + if (message instanceof MergedWarpMessage) { + AbstractResultMessage[] results = new AbstractResultMessage[((MergedWarpMessage) message).msgs.size()]; + for (int i = 0; i < results.length; i++) { + final AbstractMessage subMessage = ((MergedWarpMessage) message).msgs.get(i); + results[i] = transactionMessageHandler.onRequest(subMessage, rpcContext); + } + MergeResultMessage resultMessage = new MergeResultMessage(); + resultMessage.setMsgs(results); + remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), resultMessage); + } else { + // the single send request message + final AbstractMessage msg = (AbstractMessage) message; + AbstractResultMessage result = transactionMessageHandler.onRequest(msg, rpcContext); + remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), result); + } + } + +} diff --git a/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnResponseProcessor.java b/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnResponseProcessor.java new file mode 100644 index 0000000..705b83a --- /dev/null +++ b/core/src/main/java/io/seata/core/rpc/processor/server/ServerOnResponseProcessor.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.processor.server; + +import io.netty.channel.ChannelHandlerContext; +import io.seata.common.util.NetUtil; +import io.seata.core.protocol.AbstractResultMessage; +import io.seata.core.protocol.MessageFuture; +import io.seata.core.protocol.RpcMessage; +import io.seata.core.protocol.transaction.BranchCommitResponse; +import io.seata.core.protocol.transaction.BranchRollbackResponse; +import io.seata.core.rpc.RpcContext; +import io.seata.core.rpc.TransactionMessageHandler; +import io.seata.core.rpc.netty.ChannelManager; +import io.seata.core.rpc.processor.RemotingProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * handle RM/TM response message. + *

    + * process message type: + * RM: + * 1) {@link BranchCommitResponse} + * 2) {@link BranchRollbackResponse} + * + * @author zhangchenghui.dev@gmail.com + * @since 1.3.0 + */ +public class ServerOnResponseProcessor implements RemotingProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerOnRequestProcessor.class); + + /** + * To handle the received RPC message on upper level. + */ + private TransactionMessageHandler transactionMessageHandler; + + /** + * The Futures from io.seata.core.rpc.netty.AbstractNettyRemoting#futures + */ + private ConcurrentMap futures; + + public ServerOnResponseProcessor(TransactionMessageHandler transactionMessageHandler, + ConcurrentHashMap futures) { + this.transactionMessageHandler = transactionMessageHandler; + this.futures = futures; + } + + @Override + public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + MessageFuture messageFuture = futures.remove(rpcMessage.getId()); + if (messageFuture != null) { + messageFuture.setResultMessage(rpcMessage.getBody()); + } else { + if (ChannelManager.isRegistered(ctx.channel())) { + onResponseMessage(ctx, rpcMessage); + } else { + try { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("closeChannelHandlerContext channel:" + ctx.channel()); + } + ctx.disconnect(); + ctx.close(); + } catch (Exception exx) { + LOGGER.error(exx.getMessage()); + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info(String.format("close a unhandled connection! [%s]", ctx.channel().toString())); + } + } + } + } + + private void onResponseMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("server received:{},clientIp:{},vgroup:{}", rpcMessage.getBody(), + NetUtil.toIpAddress(ctx.channel().remoteAddress()), + ChannelManager.getContextFromIdentified(ctx.channel()).getTransactionServiceGroup()); + } else { + try { + BatchLogHandler.INSTANCE.getLogQueue() + .put(rpcMessage.getBody() + ",clientIp:" + NetUtil.toIpAddress(ctx.channel().remoteAddress()) + ",vgroup:" + + ChannelManager.getContextFromIdentified(ctx.channel()).getTransactionServiceGroup()); + } catch (InterruptedException e) { + LOGGER.error("put message to logQueue error: {}", e.getMessage(), e); + } + } + if (rpcMessage.getBody() instanceof AbstractResultMessage) { + RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel()); + transactionMessageHandler.onResponse((AbstractResultMessage) rpcMessage.getBody(), rpcContext); + } + } +} diff --git a/core/src/main/java/io/seata/core/serializer/Serializer.java b/core/src/main/java/io/seata/core/serializer/Serializer.java new file mode 100644 index 0000000..acd0e5f --- /dev/null +++ b/core/src/main/java/io/seata/core/serializer/Serializer.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.serializer; + +/** + * The interface Codec. + * + * @author zhangsen + */ +public interface Serializer { + + /** + * Encode object to byte[]. + * + * @param the type parameter + * @param t the t + * @return the byte [ ] + */ + byte[] serialize(T t); + + /** + * Decode t from byte[]. + * + * @param the type parameter + * @param bytes the bytes + * @return the t + */ + T deserialize(byte[] bytes); +} diff --git a/core/src/main/java/io/seata/core/serializer/SerializerClassRegistry.java b/core/src/main/java/io/seata/core/serializer/SerializerClassRegistry.java new file mode 100644 index 0000000..41d9218 --- /dev/null +++ b/core/src/main/java/io/seata/core/serializer/SerializerClassRegistry.java @@ -0,0 +1,159 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.serializer; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeSet; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.core.protocol.MergeResultMessage; +import io.seata.core.protocol.MergedWarpMessage; +import io.seata.core.protocol.RegisterRMRequest; +import io.seata.core.protocol.RegisterRMResponse; +import io.seata.core.protocol.RegisterTMRequest; +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.protocol.transaction.BranchCommitRequest; +import io.seata.core.protocol.transaction.BranchCommitResponse; +import io.seata.core.protocol.transaction.BranchRegisterRequest; +import io.seata.core.protocol.transaction.BranchRegisterResponse; +import io.seata.core.protocol.transaction.BranchReportRequest; +import io.seata.core.protocol.transaction.BranchReportResponse; +import io.seata.core.protocol.transaction.BranchRollbackRequest; +import io.seata.core.protocol.transaction.BranchRollbackResponse; +import io.seata.core.protocol.transaction.GlobalBeginRequest; +import io.seata.core.protocol.transaction.GlobalBeginResponse; +import io.seata.core.protocol.transaction.GlobalCommitRequest; +import io.seata.core.protocol.transaction.GlobalCommitResponse; +import io.seata.core.protocol.transaction.GlobalLockQueryRequest; +import io.seata.core.protocol.transaction.GlobalLockQueryResponse; +import io.seata.core.protocol.transaction.GlobalReportRequest; +import io.seata.core.protocol.transaction.GlobalReportResponse; +import io.seata.core.protocol.transaction.GlobalRollbackRequest; +import io.seata.core.protocol.transaction.GlobalRollbackResponse; +import io.seata.core.protocol.transaction.GlobalStatusRequest; +import io.seata.core.protocol.transaction.GlobalStatusResponse; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; + +/** + * Provide a unified serialization registry, this class used for {@code seata-serializer-fst} + * and {@code seata-serializer-kryo}, it will register some classes at startup time (for example {@link KryoSerializerFactory#create}) + * @author funkye + */ +public class SerializerClassRegistry { + + private static final Map, Object> REGISTRATIONS = new LinkedHashMap<>(); + + static { + + // register commonly class + registerClass(HashMap.class); + registerClass(ArrayList.class); + registerClass(LinkedList.class); + registerClass(HashSet.class); + registerClass(TreeSet.class); + registerClass(Hashtable.class); + registerClass(Date.class); + registerClass(Calendar.class); + registerClass(ConcurrentHashMap.class); + registerClass(SimpleDateFormat.class); + registerClass(GregorianCalendar.class); + registerClass(Vector.class); + registerClass(BitSet.class); + registerClass(StringBuffer.class); + registerClass(StringBuilder.class); + registerClass(Object.class); + registerClass(Object[].class); + registerClass(String[].class); + registerClass(byte[].class); + registerClass(char[].class); + registerClass(int[].class); + registerClass(float[].class); + registerClass(double[].class); + + // register seata protocol relation class + registerClass(BranchCommitRequest.class); + registerClass(BranchCommitResponse.class); + registerClass(BranchRegisterRequest.class); + registerClass(BranchRegisterResponse.class); + registerClass(BranchReportRequest.class); + registerClass(BranchReportResponse.class); + registerClass(BranchRollbackRequest.class); + registerClass(BranchRollbackResponse.class); + registerClass(GlobalBeginRequest.class); + registerClass(GlobalBeginResponse.class); + registerClass(GlobalCommitRequest.class); + registerClass(GlobalCommitResponse.class); + registerClass(GlobalLockQueryRequest.class); + registerClass(GlobalLockQueryResponse.class); + registerClass(GlobalRollbackRequest.class); + registerClass(GlobalRollbackResponse.class); + registerClass(GlobalStatusRequest.class); + registerClass(GlobalStatusResponse.class); + registerClass(UndoLogDeleteRequest.class); + registerClass(GlobalReportRequest.class); + registerClass(GlobalReportResponse.class); + + registerClass(MergedWarpMessage.class); + registerClass(MergeResultMessage.class); + registerClass(RegisterRMRequest.class); + registerClass(RegisterRMResponse.class); + registerClass(RegisterTMRequest.class); + registerClass(RegisterTMResponse.class); + } + + /** + * only supposed to be called at startup time + * + * @param clazz object type + */ + public static void registerClass(Class clazz) { + registerClass(clazz, null); + } + + /** + * only supposed to be called at startup time + * + * @param clazz object type + * @param serializer object serializer + */ + public static void registerClass(Class clazz, Object serializer) { + if (clazz == null) { + throw new IllegalArgumentException("Class registered cannot be null!"); + } + REGISTRATIONS.put(clazz, serializer); + } + + /** + * get registered classes + * + * @return class serializer + * */ + public static Map, Object> getRegisteredClasses() { + return REGISTRATIONS; + } +} diff --git a/core/src/main/java/io/seata/core/serializer/SerializerType.java b/core/src/main/java/io/seata/core/serializer/SerializerType.java new file mode 100644 index 0000000..de1c59e --- /dev/null +++ b/core/src/main/java/io/seata/core/serializer/SerializerType.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.serializer; + +/** + * The enum serialize type. + * + * @author leizhiyuan + */ +public enum SerializerType { + + /** + * The seata. + *

    + * Math.pow(2, 0) + */ + SEATA((byte)0x1), + + /** + * The protobuf. + *

    + * Math.pow(2, 1) + */ + PROTOBUF((byte)0x2), + + /** + * The kryo. + *

    + * Math.pow(2, 2) + */ + KRYO((byte)0x4), + + /** + * The fst. + *

    + * Math.pow(2, 3) + */ + FST((byte)0x8), + + /** + * The hessian. + *

    + * Math.pow(2, 4) + */ + HESSIAN((byte)0x16), + ; + + private final byte code; + + SerializerType(final byte code) { + this.code = code; + } + + /** + * Gets result code. + * + * @param code the code + * @return the result code + */ + public static SerializerType getByCode(int code) { + for (SerializerType b : SerializerType.values()) { + if (code == b.code) { + return b; + } + } + throw new IllegalArgumentException("unknown codec:" + code); + } + + /** + * Gets result code. + * + * @param name the name + * @return the result code + */ + public static SerializerType getByName(String name) { + for (SerializerType b : SerializerType.values()) { + if (b.name().equalsIgnoreCase(name)) { + return b; + } + } + throw new IllegalArgumentException("unknown codec:" + name); + } + + /** + * Gets code. + * + * @return the code + */ + public byte getCode() { + return code; + } +} diff --git a/core/src/main/java/io/seata/core/store/BranchTransactionDO.java b/core/src/main/java/io/seata/core/store/BranchTransactionDO.java new file mode 100644 index 0000000..d38717e --- /dev/null +++ b/core/src/main/java/io/seata/core/store/BranchTransactionDO.java @@ -0,0 +1,255 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + +import java.util.Date; + +import io.seata.common.util.StringUtils; +import io.seata.core.model.BranchStatus; + +/** + * branch transaction data object + * + * @author zhangsen + */ +public class BranchTransactionDO { + + private String xid; + + private Long transactionId; + + private Long branchId; + + private String resourceGroupId; + + private String resourceId; + + private String branchType; + + private Integer status = BranchStatus.Unknown.getCode(); + + private String clientId; + + private String applicationData; + + private Date gmtCreate; + + private Date gmtModified; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets resource group id. + * + * @return the resource group id + */ + public String getResourceGroupId() { + return resourceGroupId; + } + + /** + * Sets resource group id. + * + * @param resourceGroupId the resource group id + */ + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets branch type. + * + * @return the branch type + */ + public String getBranchType() { + return branchType; + } + + /** + * Sets branch type. + * + * @param branchType the branch type + */ + public void setBranchType(String branchType) { + this.branchType = branchType; + } + + /** + * Gets status. + * + * @return the status + */ + public int getStatus() { + return status; + } + + /** + * Sets status. + * + * @param status the status + */ + public void setStatus(int status) { + this.status = status; + } + + /** + * Gets client id. + * + * @return the client id + */ + public String getClientId() { + return clientId; + } + + /** + * Sets client id. + * + * @param clientId the client id + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + /** + * Gets gmt create. + * + * @return the gmt create + */ + public Date getGmtCreate() { + return gmtCreate; + } + + /** + * Sets gmt create. + * + * @param gmtCreate the gmt create + */ + public void setGmtCreate(Date gmtCreate) { + this.gmtCreate = gmtCreate; + } + + /** + * Gets gmt modified. + * + * @return the gmt modified + */ + public Date getGmtModified() { + return gmtModified; + } + + /** + * Sets gmt modified. + * + * @param gmtModified the gmt modified + */ + public void setGmtModified(Date gmtModified) { + this.gmtModified = gmtModified; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java b/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java new file mode 100644 index 0000000..d94d2bc --- /dev/null +++ b/core/src/main/java/io/seata/core/store/GlobalTransactionDO.java @@ -0,0 +1,286 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + +import io.seata.common.util.StringUtils; + +import java.util.Date; + +/** + * Global Transaction data object + * + * @author zhangsen + */ +public class GlobalTransactionDO { + + private String xid; + + private Long transactionId; + + private Integer status; + + private String applicationId; + + private String transactionServiceGroup; + + private String transactionName; + + private Integer timeout; + + private Long beginTime; + + private String applicationData; + + private Date gmtCreate; + + private Date gmtModified; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets status. + * + * @return the status + */ + public int getStatus() { + return status; + } + + /** + * Sets status. + * + * @param status the status + */ + public void setStatus(int status) { + this.status = status; + } + + /** + * Gets application id. + * + * @return the application id + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Gets transaction service group. + * + * @return the transaction service group + */ + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + /** + * Sets transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Gets transaction name. + * + * @return the transaction name + */ + public String getTransactionName() { + return transactionName; + } + + /** + * Sets transaction name. + * + * @param transactionName the transaction name + */ + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + /** + * Gets timeout. + * + * @return the timeout + */ + public int getTimeout() { + return timeout; + } + + /** + * Sets timeout. + * + * @param timeout the timeout + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Gets begin time. + * + * @return the begin time + */ + public long getBeginTime() { + return beginTime; + } + + /** + * Sets begin time. + * + * @param beginTime the begin time + */ + public void setBeginTime(long beginTime) { + this.beginTime = beginTime; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + /** + * Gets gmt create. + * + * @return the gmt create + */ + public Date getGmtCreate() { + return gmtCreate; + } + + /** + * Sets gmt create. + * + * @param gmtCreate the gmt create + */ + public void setGmtCreate(Date gmtCreate) { + this.gmtCreate = gmtCreate; + } + + /** + * Gets gmt modified. + * + * @return the gmt modified + */ + public Date getGmtModified() { + return gmtModified; + } + + /** + * Sets gmt modified. + * + * @param gmtModified the gmt modified + */ + public void setGmtModified(Date gmtModified) { + this.gmtModified = gmtModified; + } + + /** + * Sets transactionId + * @param transactionId the transactionId + */ + public void setTransactionId(Long transactionId) { + this.transactionId = transactionId; + } + + /** + * Sets status + * @param status the status + */ + public void setStatus(Integer status) { + this.status = status; + } + + /** + * Sets timeout + * @param timeout the timeout + */ + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + /** + * Sets begin time + * @param beginTime the begin time + */ + public void setBeginTime(Long beginTime) { + this.beginTime = beginTime; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/core/src/main/java/io/seata/core/store/LockDO.java b/core/src/main/java/io/seata/core/store/LockDO.java new file mode 100644 index 0000000..eb7b70b --- /dev/null +++ b/core/src/main/java/io/seata/core/store/LockDO.java @@ -0,0 +1,177 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + +import io.seata.common.util.StringUtils; + +/** + * The type Lock do. + * + * @author zhangsen + */ +public class LockDO { + + private String xid; + + private Long transactionId; + + private Long branchId; + + private String resourceId; + + private String tableName; + + private String pk; + + private String rowKey; + + /** + * Instantiates a new Lock do. + */ + public LockDO() { + } + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public Long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(Long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets row key. + * + * @return the row key + */ + public String getRowKey() { + return rowKey; + } + + /** + * Sets row key. + * + * @param rowKey the row key + */ + public void setRowKey(String rowKey) { + this.rowKey = rowKey; + } + + /** + * Gets table name. + * + * @return the table name + */ + public String getTableName() { + return tableName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets pk. + * + * @return the pk + */ + public String getPk() { + return pk; + } + + /** + * Sets pk. + * + * @param pk the pk + */ + public void setPk(String pk) { + this.pk = pk; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public Long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(Long branchId) { + this.branchId = branchId; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } +} diff --git a/core/src/main/java/io/seata/core/store/LockStore.java b/core/src/main/java/io/seata/core/store/LockStore.java new file mode 100644 index 0000000..9b81a41 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/LockStore.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + +import java.util.List; + +/** + * The interface Lock store. + * + * @author zhangsen + */ +public interface LockStore { + + /** + * Acquire lock boolean. + * + * @param lockDO the lock do + * @return the boolean + */ + boolean acquireLock(LockDO lockDO); + + + /** + * Acquire lock boolean. + * + * @param lockDOs the lock d os + * @return the boolean + */ + boolean acquireLock(List lockDOs); + + /** + * Un lock boolean. + * + * @param lockDO the lock do + * @return the boolean + */ + boolean unLock(LockDO lockDO); + + /** + * Un lock boolean. + * + * @param lockDOs the lock d os + * @return the boolean + */ + boolean unLock(List lockDOs); + + boolean unLock(String xid, Long branchId); + + boolean unLock(String xid, List branchIds); + + /** + * Is lockable boolean. + * + * @param lockDOs the lock do + * @return the boolean + */ + boolean isLockable(List lockDOs); +} diff --git a/core/src/main/java/io/seata/core/store/LogStore.java b/core/src/main/java/io/seata/core/store/LogStore.java new file mode 100644 index 0000000..4ecf801 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/LogStore.java @@ -0,0 +1,126 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + + +import java.util.List; + +/** + * the transaction log store + * + * @author zhangsen + */ +public interface LogStore { + + /** + * Query global transaction do global transaction do. + * + * @param xid the xid + * @return the global transaction do + */ + GlobalTransactionDO queryGlobalTransactionDO(String xid); + + /** + * Query global transaction do global transaction do. + * + * @param transactionId the transaction id + * @return the global transaction do + */ + GlobalTransactionDO queryGlobalTransactionDO(long transactionId); + + /** + * Query global transaction do list. + * + * @param status the status + * @param limit the limit + * @return the list + */ + List queryGlobalTransactionDO(int[] status, int limit); + + /** + * Insert global transaction do boolean. + * + * @param globalTransactionDO the global transaction do + * @return the boolean + */ + boolean insertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO); + + /** + * Update global transaction do boolean. + * + * @param globalTransactionDO the global transaction do + * @return the boolean + */ + boolean updateGlobalTransactionDO(GlobalTransactionDO globalTransactionDO); + + /** + * Delete global transaction do boolean. + * + * @param globalTransactionDO the global transaction do + * @return the boolean + */ + boolean deleteGlobalTransactionDO(GlobalTransactionDO globalTransactionDO); + + /** + * Query branch transaction do list. + * + * @param xid the xid + * @return the BranchTransactionDO list + */ + List queryBranchTransactionDO(String xid); + + /** + * Query branch transaction do list. + * + * @param xids the xid list + * @return the BranchTransactionDO list + */ + List queryBranchTransactionDO(List xids); + + /** + * Insert branch transaction do boolean. + * + * @param branchTransactionDO the branch transaction do + * @return the boolean + */ + boolean insertBranchTransactionDO(BranchTransactionDO branchTransactionDO); + + /** + * Update branch transaction do boolean. + * + * @param branchTransactionDO the branch transaction do + * @return the boolean + */ + boolean updateBranchTransactionDO(BranchTransactionDO branchTransactionDO); + + /** + * Delete branch transaction do boolean. + * + * @param branchTransactionDO the branch transaction do + * @return the boolean + */ + boolean deleteBranchTransactionDO(BranchTransactionDO branchTransactionDO); + + /** + * Gets current max session id. + * + * @param high the high + * @param low the low + * @return the current max session id + */ + long getCurrentMaxSessionId(long high, long low); + +} diff --git a/core/src/main/java/io/seata/core/store/StoreMode.java b/core/src/main/java/io/seata/core/store/StoreMode.java new file mode 100644 index 0000000..6cdef20 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/StoreMode.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store; + +/** + * transaction log store mode + * + * @author zhangsen + */ +public enum StoreMode { + + /** + * file store + */ + FILE("file"), + + /** + * database store + */ + DB("db"), + + /** + * redis store + */ + REDIS("redis"); + + private String name; + + StoreMode(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + /** + * get value of store mode + * @param name the mode name + * @return the store mode + */ + public static StoreMode get(String name) { + for (StoreMode sm : StoreMode.class.getEnumConstants()) { + if (sm.name.equalsIgnoreCase(name)) { + return sm; + } + } + throw new IllegalArgumentException("unknown store mode:" + name); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/AbstractDataSourceProvider.java b/core/src/main/java/io/seata/core/store/db/AbstractDataSourceProvider.java new file mode 100644 index 0000000..45c0d69 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/AbstractDataSourceProvider.java @@ -0,0 +1,258 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; +import javax.sql.DataSource; +import io.seata.common.exception.StoreException; +import io.seata.common.executor.Initialize; +import io.seata.common.util.ConfigTools; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.constants.DBType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The abstract datasource provider + * + * @author zhangsen + * @author will + */ +public abstract class AbstractDataSourceProvider implements DataSourceProvider, Initialize { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDataSourceProvider.class); + + private DataSource dataSource; + + /** + * The constant CONFIG. + */ + protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private final static String MYSQL_DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver"; + + private final static String MYSQL8_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; + + private final static String MYSQL_DRIVER_FILE_PREFIX = "mysql-connector-java-"; + + private final static Map MYSQL_DRIVER_LOADERS; + + private static final int DEFAULT_DB_MAX_CONN = 20; + + private static final int DEFAULT_DB_MIN_CONN = 1; + + private static final long DEFAULT_DB_MAX_WAIT = 5000; + + static { + MYSQL_DRIVER_LOADERS = createMysqlDriverClassLoaders(); + } + + @Override + public void init() { + this.dataSource = generate(); + } + + @Override + public DataSource provide() { + return this.dataSource; + } + + /** + * generate the datasource + * @return datasource + */ + public abstract DataSource generate(); + + /** + * Get db type db type. + * + * @return the db type + */ + protected DBType getDBType() { + return DBType.valueof(CONFIG.getConfig(ConfigurationKeys.STORE_DB_TYPE)); + } + + /** + * get db driver class name + * + * @return the db driver class name + */ + protected String getDriverClassName() { + String driverClassName = CONFIG.getConfig(ConfigurationKeys.STORE_DB_DRIVER_CLASS_NAME); + if (StringUtils.isBlank(driverClassName)) { + throw new StoreException( + String.format("the {%s} can't be empty", ConfigurationKeys.STORE_DB_DRIVER_CLASS_NAME)); + } + return driverClassName; + } + + /** + * get db max wait + * + * @return the db max wait + */ + protected Long getMaxWait() { + Long maxWait = CONFIG.getLong(ConfigurationKeys.STORE_DB_MAX_WAIT, DEFAULT_DB_MAX_WAIT); + return maxWait; + } + + protected ClassLoader getDriverClassLoader() { + return MYSQL_DRIVER_LOADERS.getOrDefault(getDriverClassName(), ClassLoader.getSystemClassLoader()); + } + + private static Map createMysqlDriverClassLoaders() { + Map loaders = new HashMap<>(); + String cp = System.getProperty("java.class.path"); + if (cp == null || cp.isEmpty()) { + return loaders; + } + Stream.of(cp.split(File.pathSeparator)) + .map(File::new) + .filter(File::exists) + .map(file -> file.isFile() ? file.getParentFile() : file) + .filter(Objects::nonNull) + .filter(File::isDirectory) + .map(file -> new File(file, "jdbc")) + .filter(File::exists) + .filter(File::isDirectory) + .distinct() + .flatMap(file -> { + File[] files = file.listFiles((f, name) -> name.startsWith(MYSQL_DRIVER_FILE_PREFIX)); + if (files != null) { + return Stream.of(files); + } else { + return Stream.of(); + } + }) + .forEach(file -> { + if (loaders.containsKey(MYSQL8_DRIVER_CLASS_NAME) && loaders.containsKey(MYSQL_DRIVER_CLASS_NAME)) { + return; + } + try { + URL url = file.toURI().toURL(); + ClassLoader loader = new URLClassLoader(new URL[]{url}, ClassLoader.getSystemClassLoader()); + try { + loader.loadClass(MYSQL8_DRIVER_CLASS_NAME); + loaders.putIfAbsent(MYSQL8_DRIVER_CLASS_NAME, loader); + } catch (ClassNotFoundException e) { + loaders.putIfAbsent(MYSQL_DRIVER_CLASS_NAME, loader); + } + } catch (MalformedURLException ignore) { + } + }); + return loaders; + } + + /** + * Get url string. + * + * @return the string + */ + protected String getUrl() { + String url = CONFIG.getConfig(ConfigurationKeys.STORE_DB_URL); + if (StringUtils.isBlank(url)) { + throw new StoreException(String.format("the {%s} can't be empty", ConfigurationKeys.STORE_DB_URL)); + } + return url; + } + + /** + * Get user string. + * + * @return the string + */ + protected String getUser() { + String user = CONFIG.getConfig(ConfigurationKeys.STORE_DB_USER); + if (StringUtils.isBlank(user)) { + throw new StoreException(String.format("the {%s} can't be empty", ConfigurationKeys.STORE_DB_USER)); + } + return user; + } + + /** + * Get password string. + * + * @return the string + */ + protected String getPassword() { + String password = CONFIG.getConfig(ConfigurationKeys.STORE_DB_PASSWORD); + String publicKey = getPublicKey(); + if (StringUtils.isNotBlank(publicKey)) { + try { + password = ConfigTools.publicDecrypt(password, publicKey); + } catch (Exception e) { + LOGGER.error( + "decryption failed,please confirm whether the ciphertext and secret key are correct! error msg: {}", + e.getMessage()); + } + } + return password; + } + + /** + * Get min conn int. + * + * @return the int + */ + protected int getMinConn() { + int minConn = CONFIG.getInt(ConfigurationKeys.STORE_DB_MIN_CONN, DEFAULT_DB_MIN_CONN); + return minConn < 0 ? DEFAULT_DB_MIN_CONN : minConn; + } + + /** + * Get max conn int. + * + * @return the int + */ + protected int getMaxConn() { + int maxConn = CONFIG.getInt(ConfigurationKeys.STORE_DB_MAX_CONN, DEFAULT_DB_MAX_CONN); + return maxConn < 0 ? DEFAULT_DB_MAX_CONN : maxConn; + } + + /** + * Get validation query string. + * + * @param dbType the db type + * @return the string + */ + protected String getValidationQuery(DBType dbType) { + if (DBType.ORACLE.equals(dbType)) { + return "select sysdate from dual"; + } else { + return "select 1"; + } + } + + /** + * Get public key. + * + * @return the string + */ + protected String getPublicKey() { + return CONFIG.getConfig(ConfigurationKeys.STORE_PUBLIC_KEY); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/DataSourceProvider.java b/core/src/main/java/io/seata/core/store/db/DataSourceProvider.java new file mode 100644 index 0000000..2d2d439 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/DataSourceProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db; + +import javax.sql.DataSource; + +/** + * The datasource provider + * @author will + */ +public interface DataSourceProvider { + + /** + * provide the datasource + * @return datasource + */ + DataSource provide(); +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/AbstractLockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/AbstractLockStoreSql.java new file mode 100644 index 0000000..bd87c7a --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/AbstractLockStoreSql.java @@ -0,0 +1,134 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.constants.ServerTableColumnsName; + +/** + * the database abstract lock store sql interface + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +public class AbstractLockStoreSql implements LockStoreSql { + + /** + * The constant CONFIG. + */ + protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + /** + * The constant LOCK_TABLE_PLACE_HOLD. + */ + protected static final String LOCK_TABLE_PLACE_HOLD = " #lock_table# "; + + /** + * The constant IN_PARAMS_PLACE_HOLD. + */ + protected static final String IN_PARAMS_PLACE_HOLD = " #in_params# "; + + /** + * The constant ALL_COLUMNS. + * xid, transaction_id, branch_id, resource_id, table_name, pk, row_key, gmt_create, gmt_modified + */ + protected static final String ALL_COLUMNS + = ServerTableColumnsName.LOCK_TABLE_XID + ", " + ServerTableColumnsName.LOCK_TABLE_TRANSACTION_ID + ", " + + ServerTableColumnsName.LOCK_TABLE_BRANCH_ID + ", " + ServerTableColumnsName.LOCK_TABLE_RESOURCE_ID + ", " + + ServerTableColumnsName.LOCK_TABLE_TABLE_NAME + ", " + ServerTableColumnsName.LOCK_TABLE_PK + ", " + + ServerTableColumnsName.LOCK_TABLE_ROW_KEY + ", " + ServerTableColumnsName.LOCK_TABLE_GMT_CREATE + ", " + + ServerTableColumnsName.LOCK_TABLE_GMT_MODIFIED; + + /** + * The constant DELETE_LOCK_SQL. + */ + private static final String DELETE_LOCK_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_ROW_KEY + " = ? and " + ServerTableColumnsName.LOCK_TABLE_XID + " = ?"; + + /** + * The constant BATCH_DELETE_LOCK_SQL. + */ + private static final String BATCH_DELETE_LOCK_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_XID + " = ? and " + ServerTableColumnsName.LOCK_TABLE_ROW_KEY + " in (" + IN_PARAMS_PLACE_HOLD + ") "; + + /** + * The constant BATCH_DELETE_LOCK_BY_BRANCH_SQL. + */ + private static final String BATCH_DELETE_LOCK_BY_BRANCH_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_XID + " = ? and " + ServerTableColumnsName.LOCK_TABLE_BRANCH_ID + " = ? "; + + + /** + * The constant BATCH_DELETE_LOCK_BY_BRANCHS_SQL. + */ + private static final String BATCH_DELETE_LOCK_BY_BRANCHS_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_XID + " = ? and " + ServerTableColumnsName.LOCK_TABLE_BRANCH_ID + " in (" + IN_PARAMS_PLACE_HOLD + ") "; + + + /** + * The constant QUERY_LOCK_SQL. + */ + private static final String QUERY_LOCK_SQL = "select " + ALL_COLUMNS + " from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_ROW_KEY + " = ? "; + + /** + * The constant CHECK_LOCK_SQL. + */ + private static final String CHECK_LOCK_SQL = "select " + ALL_COLUMNS + " from " + LOCK_TABLE_PLACE_HOLD + + " where " + ServerTableColumnsName.LOCK_TABLE_ROW_KEY + " in (" + IN_PARAMS_PLACE_HOLD + ")"; + + + @Override + public String getInsertLockSQL(String lockTable) { + throw new NotSupportYetException("unknown dbType:" + CONFIG.getConfig(ConfigurationKeys.STORE_DB_TYPE)); + } + + @Override + public String getDeleteLockSql(String lockTable) { + return DELETE_LOCK_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + + @Override + public String getBatchDeleteLockSql(String lockTable, String paramPlaceHold) { + return BATCH_DELETE_LOCK_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable).replace(IN_PARAMS_PLACE_HOLD, + paramPlaceHold); + } + + @Override + public String getBatchDeleteLockSqlByBranch(String lockTable) { + return BATCH_DELETE_LOCK_BY_BRANCH_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + + @Override + public String getBatchDeleteLockSqlByBranchs(String lockTable, String paramPlaceHold) { + return BATCH_DELETE_LOCK_BY_BRANCHS_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable).replace(IN_PARAMS_PLACE_HOLD, + paramPlaceHold); + } + + @Override + public String getQueryLockSql(String lockTable) { + return QUERY_LOCK_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + + @Override + public String getCheckLockableSql(String lockTable, String paramPlaceHold) { + return CHECK_LOCK_SQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable).replace(IN_PARAMS_PLACE_HOLD, paramPlaceHold); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/H2LockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/H2LockStoreSql.java new file mode 100644 index 0000000..a9227dd --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/H2LockStoreSql.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.loader.LoadLevel; + +/** + * the database lock store H2 sql + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +@LoadLevel(name = "h2") +public class H2LockStoreSql extends AbstractLockStoreSql { + + /** + * The constant INSERT_LOCK_SQL_H2. + */ + private static final String INSERT_LOCK_SQL_H2 = "insert into " + LOCK_TABLE_PLACE_HOLD + "(" + ALL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, now(), now())"; + + @Override + public String getInsertLockSQL(String lockTable) { + return INSERT_LOCK_SQL_H2.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSql.java new file mode 100644 index 0000000..20d6e62 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSql.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +/** + * the database lock store sql interface + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +public interface LockStoreSql { + + /** + * Get insert lock sql string. + * + * @param lockTable the lock table + * @return the string + */ + String getInsertLockSQL(String lockTable); + + /** + * Get delete lock sql string. + * + * @param lockTable the lock table + * @return the string + */ + String getDeleteLockSql(String lockTable); + + /** + * Get batch delete lock sql string. + * + * @param lockTable the lock table + * @param paramPlaceHold the param place hold + * @return the string + */ + String getBatchDeleteLockSql(String lockTable, String paramPlaceHold); + + /** + * Get batch delete lock sql string. + * + * @param lockTable the lock table + * @return the string + */ + String getBatchDeleteLockSqlByBranch(String lockTable); + + /** + * Get batch delete lock sql string. + * + * @param lockTable the lock table + * @param paramPlaceHold the param place hold + * @return the string + */ + String getBatchDeleteLockSqlByBranchs(String lockTable, String paramPlaceHold); + + /** + * Get query lock sql string. + * + * @param lockTable the lock table + * @return the string + */ + String getQueryLockSql(String lockTable); + + /** + * Get check lock sql string. + * + * @param lockTable the lock table + * @param paramPlaceHold the param place hold + * @return the string + */ + String getCheckLockableSql(String lockTable, String paramPlaceHold); + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactory.java b/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactory.java new file mode 100644 index 0000000..4cc711b --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import com.google.common.collect.Maps; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +import java.util.Map; + +/** + * the database lock store factory + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +public class LockStoreSqlFactory { + + private static Map LOCK_STORE_SQL_MAP = Maps.newConcurrentMap(); + + /** + * get the lock store sql + * + * @param dbType the dbType, support mysql/oracle/h2/postgre/oceanbase + * @return lock store sql + */ + public static LockStoreSql getLogStoreSql(String dbType) { + return CollectionUtils.computeIfAbsent(LOCK_STORE_SQL_MAP, dbType, + key -> EnhancedServiceLoader.load(LockStoreSql.class, dbType.toLowerCase())); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/MysqlLockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/MysqlLockStoreSql.java new file mode 100644 index 0000000..c7bb79a --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/MysqlLockStoreSql.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.loader.LoadLevel; + +/** + * the database lock store mysql sql + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +@LoadLevel(name = "mysql") +public class MysqlLockStoreSql extends AbstractLockStoreSql { + + /** + * The constant INSERT_LOCK_SQL_MYSQL. + */ + private static final String INSERT_LOCK_SQL_MYSQL = "insert into " + LOCK_TABLE_PLACE_HOLD + "(" + ALL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, now(), now())"; + + @Override + public String getInsertLockSQL(String lockTable) { + return INSERT_LOCK_SQL_MYSQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/OceanbaseLockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/OceanbaseLockStoreSql.java new file mode 100644 index 0000000..2d41009 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/OceanbaseLockStoreSql.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.loader.LoadLevel; + +/** + * the database lock store oceanbase sql + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +@LoadLevel(name = "oceanbase") +public class OceanbaseLockStoreSql extends AbstractLockStoreSql { + + /** + * The constant INSERT_LOCK_SQL_OCEANBASE. + */ + private static final String INSERT_LOCK_SQL_OCEANBASE = "insert into " + LOCK_TABLE_PLACE_HOLD + "(" + ALL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, now(), now())"; + + @Override + public String getInsertLockSQL(String lockTable) { + return INSERT_LOCK_SQL_OCEANBASE.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/OracleLockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/OracleLockStoreSql.java new file mode 100644 index 0000000..f520b4b --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/OracleLockStoreSql.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.loader.LoadLevel; + +/** + * the database lock store oracle sql + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +@LoadLevel(name = "oracle") +public class OracleLockStoreSql extends AbstractLockStoreSql { + + /** + * The constant INSERT_LOCK_SQL_ORACLE. + */ + private static final String INSERT_LOCK_SQL_ORACLE = "insert into " + LOCK_TABLE_PLACE_HOLD + "(" + ALL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, sysdate, sysdate)"; + + @Override + public String getInsertLockSQL(String lockTable) { + return INSERT_LOCK_SQL_ORACLE.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/lock/PostgresqlLockStoreSql.java b/core/src/main/java/io/seata/core/store/db/sql/lock/PostgresqlLockStoreSql.java new file mode 100644 index 0000000..9d323b5 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/lock/PostgresqlLockStoreSql.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import io.seata.common.loader.LoadLevel; + +/** + * the database lock store postgre sql + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +@LoadLevel(name = "postgresql") +public class PostgresqlLockStoreSql extends AbstractLockStoreSql { + + /** + * The constant INSERT_LOCK_SQL_POSTGRESQL. + */ + private static final String INSERT_LOCK_SQL_POSTGRESQL = "insert into " + LOCK_TABLE_PLACE_HOLD + "(" + ALL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, now(), now())"; + + @Override + public String getInsertLockSQL(String lockTable) { + return INSERT_LOCK_SQL_POSTGRESQL.replace(LOCK_TABLE_PLACE_HOLD, lockTable); + } + +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/AbstractLogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/AbstractLogStoreSqls.java new file mode 100644 index 0000000..fb0ceaf --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/AbstractLogStoreSqls.java @@ -0,0 +1,198 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.core.constants.ServerTableColumnsName; + + +/** + * The type Abstract log store sqls + * @author will + */ +public abstract class AbstractLogStoreSqls implements LogStoreSqls { + + /** + * The constant GLOBAL_TABLE_PLACEHOLD. + */ + public static final String GLOBAL_TABLE_PLACEHOLD = " #global_table# "; + + /** + * The constant BRANCH_TABLE_PLACEHOLD. + */ + public static final String BRANCH_TABLE_PLACEHOLD = " #branch_table# "; + + /** + * The constant PRAMETER_PLACEHOLD. + * format: ?, ?, ? + */ + public static final String PRAMETER_PLACEHOLD = " #PRAMETER_PLACEHOLD# "; + + /** + * The constant ALL_GLOBAL_COLUMNS. + * xid, transaction_id, status, application_id, transaction_service_group, transaction_name, timeout, begin_time, application_data, gmt_create, gmt_modified + */ + public static final String ALL_GLOBAL_COLUMNS + = ServerTableColumnsName.GLOBAL_TABLE_XID + ", " + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID + ", " + + ServerTableColumnsName.GLOBAL_TABLE_STATUS + ", " + ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_ID + ", " + + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_SERVICE_GROUP + ", " + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_NAME + ", " + + ServerTableColumnsName.GLOBAL_TABLE_TIMEOUT + ", " + ServerTableColumnsName.GLOBAL_TABLE_BEGIN_TIME + ", " + + ServerTableColumnsName.GLOBAL_TABLE_APPLICATION_DATA + ", " + + ServerTableColumnsName.GLOBAL_TABLE_GMT_CREATE + ", " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED; + + /** + * The constant ALL_BRANCH_COLUMNS. + * xid, transaction_id, branch_id, resource_group_id, resource_id, lock_key, branch_type, status, client_id, application_data, gmt_create, gmt_modified + */ + protected static final String ALL_BRANCH_COLUMNS + = ServerTableColumnsName.BRANCH_TABLE_XID + ", " + ServerTableColumnsName.BRANCH_TABLE_TRANSACTION_ID + ", " + + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + ", " + ServerTableColumnsName.BRANCH_TABLE_RESOURCE_GROUP_ID + ", " + + ServerTableColumnsName.BRANCH_TABLE_RESOURCE_ID + ", " + + ServerTableColumnsName.BRANCH_TABLE_BRANCH_TYPE + ", " + ServerTableColumnsName.BRANCH_TABLE_STATUS + ", " + + ServerTableColumnsName.BRANCH_TABLE_CLIENT_ID + ", " + ServerTableColumnsName.BRANCH_TABLE_APPLICATION_DATA + ", " + + ServerTableColumnsName.BRANCH_TABLE_GMT_CREATE + ", " + ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED; + + /** + * The constant DELETE_GLOBAL_TRANSACTION. + */ + public static final String DELETE_GLOBAL_TRANSACTION = "delete from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_XID + " = ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION. + */ + public static final String QUERY_GLOBAL_TRANSACTION = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_XID + " = ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_ID. + */ + public static final String QUERY_GLOBAL_TRANSACTION_BY_ID = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID + " = ?"; + + /** + * The constant DELETE_BRANCH_TRANSACTION_BY_BRANCH_ID. + */ + public static final String DELETE_BRANCH_TRANSACTION_BY_BRANCH_ID = "delete from " + BRANCH_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?" + + " and " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " = ?"; + + /** + * The constant DELETE_BRANCH_TRANSACTION_BY_XID. + */ + public static final String DELETE_BRANCH_TRANSACTION_BY_XID = "delete from " + BRANCH_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?"; + + + /** + * The constant QUERY_BRANCH_TRANSACTION. + */ + public static final String QUERY_BRANCH_TRANSACTION = "select " + ALL_BRANCH_COLUMNS + + " from " + BRANCH_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?" + + " order by " + ServerTableColumnsName.BRANCH_TABLE_GMT_CREATE + " asc"; + + /** + * The constant QUERY_BRANCH_TRANSACTION_XIDS. + */ + public static final String QUERY_BRANCH_TRANSACTION_XIDS = "select " + ALL_BRANCH_COLUMNS + + " from " + BRANCH_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " in (" + PRAMETER_PLACEHOLD + ")" + + " order by " + ServerTableColumnsName.BRANCH_TABLE_GMT_CREATE + " asc"; + + /** + * The constant CHECK_MAX_TRANS_ID. + */ + public static final String QUERY_MAX_TRANS_ID = "select max(" + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID + ")" + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID + " < ?" + + " and " + ServerTableColumnsName.GLOBAL_TABLE_TRANSACTION_ID + " > ?"; + + /** + * The constant CHECK_MAX_BTANCH_ID. + */ + public static final String QUERY_MAX_BTANCH_ID = "select max(" + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + ")" + + " from " + BRANCH_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " < ?" + + " and " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " > ?"; + + @Override + public abstract String getInsertGlobalTransactionSQL(String globalTable); + + @Override + public abstract String getUpdateGlobalTransactionStatusSQL(String globalTable); + + @Override + public String getDeleteGlobalTransactionSQL(String globalTable) { + return DELETE_GLOBAL_TRANSACTION.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryGlobalTransactionSQL(String globalTable) { + return QUERY_GLOBAL_TRANSACTION.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryGlobalTransactionSQLByTransactionId(String globalTable) { + return QUERY_GLOBAL_TRANSACTION_BY_ID.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public abstract String getQueryGlobalTransactionSQLByStatus(String globalTable, String paramsPlaceHolder); + + @Override + public abstract String getQueryGlobalTransactionForRecoverySQL(String globalTable); + + + @Override + public abstract String getInsertBranchTransactionSQL(String branchTable); + + @Override + public abstract String getUpdateBranchTransactionStatusSQL(String branchTable); + + @Override + public String getDeleteBranchTransactionByBranchIdSQL(String branchTable) { + return DELETE_BRANCH_TRANSACTION_BY_BRANCH_ID.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getDeleteBranchTransactionByXId(String branchTable) { + return DELETE_BRANCH_TRANSACTION_BY_XID.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getQueryBranchTransaction(String branchTable) { + return QUERY_BRANCH_TRANSACTION.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getQueryBranchTransaction(String branchTable, String paramsPlaceHolder) { + return QUERY_BRANCH_TRANSACTION_XIDS.replace(BRANCH_TABLE_PLACEHOLD, branchTable) + .replace(PRAMETER_PLACEHOLD, paramsPlaceHolder); + } + + + @Override + public String getQueryGlobalMax(String globalTable) { + return QUERY_MAX_TRANS_ID.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryBranchMax(String branchTable) { + return QUERY_MAX_BTANCH_ID.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/H2LogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/H2LogStoreSqls.java new file mode 100644 index 0000000..894c40f --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/H2LogStoreSqls.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.LoadLevel; + +/** + * Database log store h2 sql + * @author will + */ +@LoadLevel(name = "h2") +public class H2LogStoreSqls extends MysqlLogStoreSqls { +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqls.java new file mode 100644 index 0000000..b6e097f --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqls.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +/** + * Database log store sql + * @author will + */ +public interface LogStoreSqls { + + /** + * Get insert global transaction sql string. + * + * @param globalTable the global table + * @return the string + */ + String getInsertGlobalTransactionSQL(String globalTable); + + /** + * Get update global transaction status sql string. + * + * @param globalTable the global table + * @return the string + */ + String getUpdateGlobalTransactionStatusSQL(String globalTable); + + /** + * Get delete global transaction sql string. + * + * @param globalTable the global table + * @return the string + */ + String getDeleteGlobalTransactionSQL(String globalTable); + + /** + * Get query global transaction sql string. + * + * @param globalTable the global table + * @return the string + */ + String getQueryGlobalTransactionSQL(String globalTable); + + /** + * Get query global transaction sql by transaction id string. + * + * @param globalTable the global table + * @return the string + */ + String getQueryGlobalTransactionSQLByTransactionId(String globalTable); + + /** + * Get query global transaction sql by status string. + * + * @param globalTable the global table + * @param paramsPlaceHolder the params place holder + * @return the string + */ + String getQueryGlobalTransactionSQLByStatus(String globalTable, String paramsPlaceHolder); + + /** + * Get query global transaction for recovery sql string. + * + * @param globalTable the global table + * @return the string + */ + String getQueryGlobalTransactionForRecoverySQL(String globalTable); + + /** + * Get insert branch transaction sql string. + * + * @param branchTable the branch table + * @return the string + */ + String getInsertBranchTransactionSQL(String branchTable); + + /** + * Get update branch transaction status sql string. + * + * @param branchTable the branch table + * @return the string + */ + String getUpdateBranchTransactionStatusSQL(String branchTable); + + /** + * Get delete branch transaction by branch id sql string. + * + * @param branchTable the branch table + * @return the string + */ + String getDeleteBranchTransactionByBranchIdSQL(String branchTable); + + /** + * Get delete branch transaction by x id string. + * + * @param branchTable the branch table + * @return the string + */ + String getDeleteBranchTransactionByXId(String branchTable); + + /** + * Get query branch transaction string. + * + * @param branchTable the branch table + * @return the string + */ + String getQueryBranchTransaction(String branchTable); + + /** + * Get query branch transaction string. + * + * @param branchTable the branch table + * @param paramsPlaceHolder the params place holder + * @return the string + */ + String getQueryBranchTransaction(String branchTable, String paramsPlaceHolder); + + /** + * Gets query global max. + * + * @param globalTable the global table + * @return the query global max + */ + String getQueryGlobalMax(String globalTable); + + /** + * Gets query branch max. + * + * @param branchTable the branch table + * @return the query branch max + */ + String getQueryBranchMax(String branchTable); +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactory.java b/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactory.java new file mode 100644 index 0000000..5fefdea --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author will + */ +public class LogStoreSqlsFactory { + + private static Map LOG_STORE_SQLS_MAP = new ConcurrentHashMap<>(); + + /** + * get the log store sqls + * @param dbType the db type + * @return the LogStoreSqls + */ + public static LogStoreSqls getLogStoreSqls(String dbType) { + return CollectionUtils.computeIfAbsent(LOG_STORE_SQLS_MAP, dbType, + key -> EnhancedServiceLoader.load(LogStoreSqls.class, dbType.toLowerCase())); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/MysqlLogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/MysqlLogStoreSqls.java new file mode 100644 index 0000000..23b8890 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/MysqlLogStoreSqls.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.constants.ServerTableColumnsName; + +/** + * Database log store mysql sql + * @author will + */ +@LoadLevel(name = "mysql") +public class MysqlLogStoreSqls extends AbstractLogStoreSqls { + + /** + * The constant INSERT_GLOBAL_TRANSACTION_MYSQL. + */ + public static final String INSERT_GLOBAL_TRANSACTION_MYSQL = "insert into " + GLOBAL_TABLE_PLACEHOLD + + "(" + ALL_GLOBAL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, now(), now())"; + + /** + * The constant UPDATE_GLOBAL_TRANSACTION_STATUS_MYSQL. + */ + public static final String UPDATE_GLOBAL_TRANSACTION_STATUS_MYSQL = "update " + GLOBAL_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + " = now()" + + " where " + ServerTableColumnsName.GLOBAL_TABLE_XID + " = ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_BY_STATUS. + */ + public static final String QUERY_GLOBAL_TRANSACTION_BY_STATUS_MYSQL = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (" + PRAMETER_PLACEHOLD + ")" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " limit ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_MYSQL. + */ + public static final String QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_MYSQL = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (0, 2, 3, 4, 5, 6, 7, 8, 10 ,12, 14)" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " limit ?"; + + /** + * The constant INSERT_BRANCH_TRANSACTION_MYSQL. + */ + public static final String INSERT_BRANCH_TRANSACTION_MYSQL = "insert into " + BRANCH_TABLE_PLACEHOLD + + "(" + ALL_BRANCH_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, now(6), now(6))"; + + /** + * The constant UPDATE_BRANCH_TRANSACTION_STATUS_MYSQL. + */ + public static final String UPDATE_BRANCH_TRANSACTION_STATUS_MYSQL = "update " + BRANCH_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.BRANCH_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED + " = now(6)" + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?" + + " and " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " = ?"; + + @Override + public String getInsertGlobalTransactionSQL(String globalTable) { + return INSERT_GLOBAL_TRANSACTION_MYSQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getUpdateGlobalTransactionStatusSQL(String globalTable) { + return UPDATE_GLOBAL_TRANSACTION_STATUS_MYSQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryGlobalTransactionSQLByStatus(String globalTable, String paramsPlaceHolder) { + return QUERY_GLOBAL_TRANSACTION_BY_STATUS_MYSQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable) + .replace(PRAMETER_PLACEHOLD, paramsPlaceHolder); + } + + @Override + public String getQueryGlobalTransactionForRecoverySQL(String globalTable) { + return QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_MYSQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getInsertBranchTransactionSQL(String branchTable) { + return INSERT_BRANCH_TRANSACTION_MYSQL.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getUpdateBranchTransactionStatusSQL(String branchTable) { + return UPDATE_BRANCH_TRANSACTION_STATUS_MYSQL.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/OceanbaseLogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/OceanbaseLogStoreSqls.java new file mode 100644 index 0000000..c62f6a7 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/OceanbaseLogStoreSqls.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.LoadLevel; + +/** + * Database log store oceanbase sql + * @author will + */ +@LoadLevel(name = "oceanbase") +public class OceanbaseLogStoreSqls extends MysqlLogStoreSqls { +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/OracleLogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/OracleLogStoreSqls.java new file mode 100644 index 0000000..c401a69 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/OracleLogStoreSqls.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.constants.ServerTableColumnsName; + +/** + * Database log store oracle sql + * @author will + */ +@LoadLevel(name = "oracle") +public class OracleLogStoreSqls extends AbstractLogStoreSqls { + + /** + * The constant INSERT_GLOBAL_TRANSACTION_ORACLE. + */ + public static final String INSERT_GLOBAL_TRANSACTION_ORACLE = "insert into " + GLOBAL_TABLE_PLACEHOLD + + "(" + ALL_GLOBAL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, sysdate, sysdate)"; + + /** + * The constant UPDATE_GLOBAL_TRANSACTION_STATUS_ORACLE. + */ + public static final String UPDATE_GLOBAL_TRANSACTION_STATUS_ORACLE = "update " + GLOBAL_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + " = sysdate" + + " where " + ServerTableColumnsName.GLOBAL_TABLE_XID + " = ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_BY_STATUS_ORACLE. + */ + public static final String QUERY_GLOBAL_TRANSACTION_BY_STATUS_ORACLE = "select A.* from (" + + " select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (" + PRAMETER_PLACEHOLD + ")" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " ) A" + + " where ROWNUM <= ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_ORACLE. + */ + public static final String QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_ORACLE = "select A.* from (" + + " select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (0, 2, 3, 4, 5, 6, 7, 8, 10 ,12, 14)" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " ) A" + + " where ROWNUM <= ?"; + + /** + * The constant INSERT_BRANCH_TRANSACTION_ORACLE. + */ + public static final String INSERT_BRANCH_TRANSACTION_ORACLE = "insert into " + BRANCH_TABLE_PLACEHOLD + + "(" + ALL_BRANCH_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, systimestamp, systimestamp)"; + + /** + * The constant UPDATE_BRANCH_TRANSACTION_STATUS_ORACLE. + */ + public static final String UPDATE_BRANCH_TRANSACTION_STATUS_ORACLE = "update " + BRANCH_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.BRANCH_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED + " = systimestamp" + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?" + + " and " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " = ?"; + + @Override + public String getInsertGlobalTransactionSQL(String globalTable) { + return INSERT_GLOBAL_TRANSACTION_ORACLE.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getUpdateGlobalTransactionStatusSQL(String globalTable) { + return UPDATE_GLOBAL_TRANSACTION_STATUS_ORACLE.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryGlobalTransactionSQLByStatus(String globalTable, String paramsPlaceHolder) { + return QUERY_GLOBAL_TRANSACTION_BY_STATUS_ORACLE.replace(GLOBAL_TABLE_PLACEHOLD, globalTable) + .replace(PRAMETER_PLACEHOLD, paramsPlaceHolder); + } + + @Override + public String getQueryGlobalTransactionForRecoverySQL(String globalTable) { + return QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_ORACLE.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getInsertBranchTransactionSQL(String branchTable) { + return INSERT_BRANCH_TRANSACTION_ORACLE.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getUpdateBranchTransactionStatusSQL(String branchTable) { + return UPDATE_BRANCH_TRANSACTION_STATUS_ORACLE.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } +} diff --git a/core/src/main/java/io/seata/core/store/db/sql/log/PostgresqlLogStoreSqls.java b/core/src/main/java/io/seata/core/store/db/sql/log/PostgresqlLogStoreSqls.java new file mode 100644 index 0000000..13eeea9 --- /dev/null +++ b/core/src/main/java/io/seata/core/store/db/sql/log/PostgresqlLogStoreSqls.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.constants.ServerTableColumnsName; + +/** + * Database log store postgresql sql + * @author will + */ +@LoadLevel(name = "postgresql") +public class PostgresqlLogStoreSqls extends AbstractLogStoreSqls { + + /** + * The constant INSERT_GLOBAL_TRANSACTION_POSTGRESQL. + */ + public static final String INSERT_GLOBAL_TRANSACTION_POSTGRESQL = "insert into " + GLOBAL_TABLE_PLACEHOLD + + "(" + ALL_GLOBAL_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, now(), now())"; + + /** + * The constant UPDATE_GLOBAL_TRANSACTION_STATUS_POSTGRESQL. + */ + public static final String UPDATE_GLOBAL_TRANSACTION_STATUS_POSTGRESQL = "update " + GLOBAL_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + " = now()" + + " where " + ServerTableColumnsName.GLOBAL_TABLE_XID + " = ?"; + + /** + * This constant QUERY_GLOBAL_TRANSACTION_BY_STATUS_POSTGRESQL. + */ + public static final String QUERY_GLOBAL_TRANSACTION_BY_STATUS_POSTGRESQL = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (" + PRAMETER_PLACEHOLD + ")" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " limit ?"; + + /** + * The constant QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_POSTGRESQL. + */ + public static final String QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_POSTGRESQL = "select " + ALL_GLOBAL_COLUMNS + + " from " + GLOBAL_TABLE_PLACEHOLD + + " where " + ServerTableColumnsName.GLOBAL_TABLE_STATUS + " in (0, 2, 3, 4, 5, 6, 7, 8, 10 ,12, 14)" + + " order by " + ServerTableColumnsName.GLOBAL_TABLE_GMT_MODIFIED + + " limit ?"; + + /** + * The constant INSERT_BRANCH_TRANSACTION_POSTGRESQL. + */ + public static final String INSERT_BRANCH_TRANSACTION_POSTGRESQL = "insert into " + BRANCH_TABLE_PLACEHOLD + + "(" + ALL_BRANCH_COLUMNS + ")" + + " values (?, ?, ?, ?, ?, ?, ?, ?, ?, now(), now())"; + + /** + * The constant UPDATE_BRANCH_TRANSACTION_STATUS_POSTGRESQL. + */ + public static final String UPDATE_BRANCH_TRANSACTION_STATUS_POSTGRESQL = "update " + BRANCH_TABLE_PLACEHOLD + + " set " + ServerTableColumnsName.BRANCH_TABLE_STATUS + " = ?," + + " " + ServerTableColumnsName.BRANCH_TABLE_GMT_MODIFIED + " = now()" + + " where " + ServerTableColumnsName.BRANCH_TABLE_XID + " = ?" + + " and " + ServerTableColumnsName.BRANCH_TABLE_BRANCH_ID + " = ?"; + + @Override + public String getInsertGlobalTransactionSQL(String globalTable) { + return INSERT_GLOBAL_TRANSACTION_POSTGRESQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getUpdateGlobalTransactionStatusSQL(String globalTable) { + return UPDATE_GLOBAL_TRANSACTION_STATUS_POSTGRESQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getQueryGlobalTransactionSQLByStatus(String globalTable, String paramsPlaceHolder) { + return QUERY_GLOBAL_TRANSACTION_BY_STATUS_POSTGRESQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable) + .replace(PRAMETER_PLACEHOLD, paramsPlaceHolder); + } + + @Override + public String getQueryGlobalTransactionForRecoverySQL(String globalTable) { + return QUERY_GLOBAL_TRANSACTION_FOR_RECOVERY_POSTGRESQL.replace(GLOBAL_TABLE_PLACEHOLD, globalTable); + } + + @Override + public String getInsertBranchTransactionSQL(String branchTable) { + return INSERT_BRANCH_TRANSACTION_POSTGRESQL.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } + + @Override + public String getUpdateBranchTransactionStatusSQL(String branchTable) { + return UPDATE_BRANCH_TRANSACTION_STATUS_POSTGRESQL.replace(BRANCH_TABLE_PLACEHOLD, branchTable); + } +} diff --git a/core/src/main/log4j.properties b/core/src/main/log4j.properties new file mode 100644 index 0000000..05ba0e3 --- /dev/null +++ b/core/src/main/log4j.properties @@ -0,0 +1,31 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +log4j.rootCategory=info,R + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Threshold=info +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=- %m%n + +log4j.appender.R=org.apache.log4j.RollingFileAppender +log4j.appender.R.Append=true +log4j.appender.R.Threshold=info +log4j.appender.R.MaxFileSize=102400KB +log4j.appender.R.MaxBackupIndex=10 +log4j.appender.R.File=${WORKDIR}/logs/biz.log +log4j.appender.R.layout=org.apache.log4j.PatternLayout +log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss} [%c]-[%p] %m%n diff --git a/core/src/main/logback.xml b/core/src/main/logback.xml new file mode 100644 index 0000000..409c0d4 --- /dev/null +++ b/core/src/main/logback.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/core/src/main/resources/META-INF/services/io.seata.core.auth.AuthSigner b/core/src/main/resources/META-INF/services/io.seata.core.auth.AuthSigner new file mode 100644 index 0000000..aaf57f7 --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.seata.core.auth.AuthSigner @@ -0,0 +1,17 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.core.auth.DefaultAuthSigner \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/io.seata.core.context.ContextCore b/core/src/main/resources/META-INF/services/io.seata.core.context.ContextCore new file mode 100644 index 0000000..ca713c7 --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.seata.core.context.ContextCore @@ -0,0 +1,2 @@ +io.seata.core.context.ThreadLocalContextCore +io.seata.core.context.FastThreadLocalContextCore \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/io.seata.core.rpc.hook.RpcHook b/core/src/main/resources/META-INF/services/io.seata.core.rpc.hook.RpcHook new file mode 100644 index 0000000..9d86e29 --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.seata.core.rpc.hook.RpcHook @@ -0,0 +1 @@ +io.seata.core.rpc.hook.StatusRpcHook \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.lock.LockStoreSql b/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.lock.LockStoreSql new file mode 100644 index 0000000..a8b5cbc --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.lock.LockStoreSql @@ -0,0 +1,5 @@ +io.seata.core.store.db.sql.lock.MysqlLockStoreSql +io.seata.core.store.db.sql.lock.OracleLockStoreSql +io.seata.core.store.db.sql.lock.OceanbaseLockStoreSql +io.seata.core.store.db.sql.lock.PostgresqlLockStoreSql +io.seata.core.store.db.sql.lock.H2LockStoreSql \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.log.LogStoreSqls b/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.log.LogStoreSqls new file mode 100644 index 0000000..42181e1 --- /dev/null +++ b/core/src/main/resources/META-INF/services/io.seata.core.store.db.sql.log.LogStoreSqls @@ -0,0 +1,5 @@ +io.seata.core.store.db.sql.log.MysqlLogStoreSqls +io.seata.core.store.db.sql.log.OracleLogStoreSqls +io.seata.core.store.db.sql.log.PostgresqlLogStoreSqls +io.seata.core.store.db.sql.log.OceanbaseLogStoreSqls +io.seata.core.store.db.sql.log.H2LogStoreSqls \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/context/ContextCoreTest.java b/core/src/test/java/io/seata/core/context/ContextCoreTest.java new file mode 100644 index 0000000..8d99fc2 --- /dev/null +++ b/core/src/test/java/io/seata/core/context/ContextCoreTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Context core test. + * + * @author guoyao + */ +public class ContextCoreTest { + + private final String FIRST_KEY = "first_key"; + private final String FIRST_VALUE = "first_value"; + private final String SECOND_KEY = "second_key"; + private final String SECOND_VALUE = "second_value"; + private final String NOT_EXIST_KEY = "not_exist_key"; + + /** + * Test put. + */ + @Test + public void testPut() { + ContextCore load = ContextCoreLoader.load(); + assertThat(load.put(FIRST_KEY, FIRST_VALUE)).isNull(); + assertThat(load.put(SECOND_KEY, SECOND_VALUE)).isNull(); + assertThat(load.put(FIRST_KEY, SECOND_VALUE)).isEqualTo(FIRST_VALUE); + assertThat(load.put(SECOND_KEY, FIRST_VALUE)).isEqualTo(SECOND_VALUE); + //clear keys + load.remove(FIRST_KEY); + load.remove(SECOND_KEY); + } + + /** + * Test get. + */ + @Test + public void testGet() { + ContextCore load = ContextCoreLoader.load(); + load.put(FIRST_KEY, FIRST_VALUE); + load.put(SECOND_KEY, FIRST_VALUE); + assertThat(load.get(FIRST_KEY)).isEqualTo(FIRST_VALUE); + assertThat(load.get(SECOND_KEY)).isEqualTo(FIRST_VALUE); + load.put(FIRST_KEY, SECOND_VALUE); + load.put(SECOND_KEY, SECOND_VALUE); + assertThat(load.get(FIRST_KEY)).isEqualTo(SECOND_VALUE); + assertThat(load.get(SECOND_KEY)).isEqualTo(SECOND_VALUE); + assertThat(load.get(NOT_EXIST_KEY)).isNull(); + //clear keys + load.remove(FIRST_KEY); + load.remove(SECOND_KEY); + load.remove(NOT_EXIST_KEY); + } + + /** + * Test remove. + */ + @Test + public void testRemove() { + ContextCore load = ContextCoreLoader.load(); + load.put(FIRST_KEY, FIRST_VALUE); + load.put(SECOND_KEY, SECOND_VALUE); + assertThat(load.remove(FIRST_KEY)).isEqualTo(FIRST_VALUE); + assertThat(load.remove(SECOND_KEY)).isEqualTo(SECOND_VALUE); + assertThat(load.remove(NOT_EXIST_KEY)).isNull(); + } + +} diff --git a/core/src/test/java/io/seata/core/context/GlobalLockConfigHolderTest.java b/core/src/test/java/io/seata/core/context/GlobalLockConfigHolderTest.java new file mode 100644 index 0000000..440f739 --- /dev/null +++ b/core/src/test/java/io/seata/core/context/GlobalLockConfigHolderTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import io.seata.core.model.GlobalLockConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class GlobalLockConfigHolderTest { + + @BeforeEach + void setUp() { + assertNull(GlobalLockConfigHolder.getCurrentGlobalLockConfig(), "should be null at first"); + } + + @Test + void setAndReturnPrevious() { + GlobalLockConfig config1 = new GlobalLockConfig(); + assertNull(GlobalLockConfigHolder.setAndReturnPrevious(config1), "should return null"); + assertSame(config1, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), "holder fail to store config"); + + GlobalLockConfig config2 = new GlobalLockConfig(); + assertSame(config1, GlobalLockConfigHolder.setAndReturnPrevious(config2), "fail to get previous config"); + assertSame(config2, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), "holder fail to store latest config"); + } + + @AfterEach + void tearDown() { + assertDoesNotThrow(GlobalLockConfigHolder::remove, "clear method should not throw anything"); + } +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/context/RootContextTest.java b/core/src/test/java/io/seata/core/context/RootContextTest.java new file mode 100644 index 0000000..710b635 --- /dev/null +++ b/core/src/test/java/io/seata/core/context/RootContextTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.context; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.core.model.BranchType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Root context test. + * + * @author guoyao + */ +public class RootContextTest { + + private final String DEFAULT_XID = "default_xid"; + + private final BranchType DEFAULT_BRANCH_TYPE = BranchType.AT; + + /** + * Test bind and unbind. + */ + @Test + public void testBind_And_Unbind() { + assertThat(RootContext.unbind()).isNull(); + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID); + + RootContext.unbind(); + assertThat(RootContext.getXID()).isNull(); + } + + /** + * Test get xid. + */ + @Test + public void testGetXID() { + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getXID()).isNull(); + } + + /** + * Test bind and unbind branchType. + */ + @Test + public void testBind_And_Unbind_BranchType() { + assertThat(RootContext.unbindBranchType()).isNull(); + RootContext.bindBranchType(DEFAULT_BRANCH_TYPE); + + //before bind xid, branchType is null + assertThat(RootContext.getBranchType()).isNull(); + //after bind xid, branchType is not null + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(DEFAULT_BRANCH_TYPE); + + //unbind xid and branchType + assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isNull(); + assertThat(RootContext.unbindBranchType()).isEqualTo(DEFAULT_BRANCH_TYPE); + assertThat(RootContext.getBranchType()).isNull(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> RootContext.bindBranchType(null)); + } + + /** + * Test get branchType. + */ + @Test + public void testGetBranchType() { + RootContext.bindBranchType(DEFAULT_BRANCH_TYPE); + + //before bind xid, branchType is null + assertThat(RootContext.getBranchType()).isNull(); + //after bind xid, branchType is not null + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(DEFAULT_BRANCH_TYPE); + + RootContext.unbind(); + assertThat(RootContext.unbindBranchType()).isEqualTo(DEFAULT_BRANCH_TYPE); + assertThat(RootContext.getBranchType()).isNull(); + } + + /** + * Test in global transaction. + */ + @Test + public void testInGlobalTransaction() { + assertThat(RootContext.inGlobalTransaction()).isFalse(); + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.inGlobalTransaction()).isTrue(); + RootContext.unbind(); + assertThat(RootContext.inGlobalTransaction()).isFalse(); + assertThat(RootContext.getXID()).isNull(); + } + + /** + * Test in tcc branch. + */ + @Test + public void testInTccBranch() { + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.inTccBranch()).isFalse(); + RootContext.bindBranchType(BranchType.TCC); + assertThat(RootContext.inTccBranch()).isTrue(); + RootContext.unbindBranchType(); + assertThat(RootContext.inTccBranch()).isFalse(); + RootContext.unbind(); + } + + /** + * Test in saga branch. + */ + @Test + public void testInSagaBranch() { + RootContext.bind(DEFAULT_XID); + assertThat(RootContext.inSagaBranch()).isFalse(); + RootContext.bindBranchType(BranchType.SAGA); + assertThat(RootContext.inSagaBranch()).isTrue(); + RootContext.unbindBranchType(); + assertThat(RootContext.inSagaBranch()).isFalse(); + RootContext.unbind(); + } + + /** + * Test assert not in global transaction with exception. + */ + @Test + public void testAssertNotInGlobalTransactionWithException() { + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + try { + RootContext.assertNotInGlobalTransaction(); + RootContext.bind(DEFAULT_XID); + RootContext.assertNotInGlobalTransaction(); + } finally { + //clear + RootContext.unbind(); + assertThat(RootContext.getXID()).isNull(); + } + }); + } + + /** + * Test assert not in global transaction. + */ + @Test + public void testAssertNotInGlobalTransaction() { + RootContext.assertNotInGlobalTransaction(); + assertThat(RootContext.getXID()).isNull(); + } + + @Test + public void testBindBranchType_And_UnbindBranchType() { + assertThat(RootContext.getBranchType()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + RootContext.bindBranchType(DEFAULT_BRANCH_TYPE); + assertThat(RootContext.unbindBranchType()).isEqualTo(DEFAULT_BRANCH_TYPE); + assertThat(RootContext.getBranchType()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + } + +} diff --git a/core/src/test/java/io/seata/core/event/GuavaEventBusTest.java b/core/src/test/java/io/seata/core/event/GuavaEventBusTest.java new file mode 100644 index 0000000..a9b306d --- /dev/null +++ b/core/src/test/java/io/seata/core/event/GuavaEventBusTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.event; + +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.eventbus.Subscribe; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test default GuavaEventBus. + * + * @author zhengyangyong + */ +public class GuavaEventBusTest { + @Test + public void test() { + + AtomicInteger counter = new AtomicInteger(0); + EventBus eventBus = new GuavaEventBus("test"); + + class TestEvent implements Event { + private final int value; + + public int getValue() { + return value; + } + + public TestEvent(int value) { + this.value = value; + } + } + + class TestSubscriber { + @Subscribe + public void process(TestEvent event) { + counter.addAndGet(event.getValue()); + } + } + + TestSubscriber subscriber = new TestSubscriber(); + eventBus.register(subscriber); + + eventBus.post(new TestEvent(1)); + + Assertions.assertEquals(1, counter.get()); + + eventBus.unregister(subscriber); + + eventBus.post(new TestEvent(1)); + + Assertions.assertEquals(1, counter.get()); + } +} diff --git a/core/src/test/java/io/seata/core/message/BranchCommitRequestTest.java b/core/src/test/java/io/seata/core/message/BranchCommitRequestTest.java new file mode 100644 index 0000000..f30d68b --- /dev/null +++ b/core/src/test/java/io/seata/core/message/BranchCommitRequestTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.model.BranchType; +import io.seata.core.protocol.transaction.BranchCommitRequest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch commit request test. + * + * @author xiajun.0706 @163.com + * @since 2019 /1/23 + */ +public class BranchCommitRequestTest { + + /** + * To string test. + * + * @throws Exception the exception + */ + @Test + public void toStringTest() { + BranchCommitRequest branchCommitRequest = new BranchCommitRequest(); + + branchCommitRequest.setXid("127.0.0.1:9999:39875642"); + branchCommitRequest.setBranchId(1); + branchCommitRequest.setBranchType(BranchType.AT); + branchCommitRequest.setResourceId("resource1"); + branchCommitRequest.setApplicationData("app1"); + + Assertions.assertEquals("xid=127.0.0.1:9999:39875642,branchId=1,branchType=AT," + + "resourceId=resource1,applicationData=app1", branchCommitRequest.toString()); + + } +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/BranchCommitResponseTest.java b/core/src/test/java/io/seata/core/message/BranchCommitResponseTest.java new file mode 100644 index 0000000..261ee33 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/BranchCommitResponseTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.model.BranchStatus; +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.BranchCommitResponse; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch commit response test. + * + * @author xiajun.0706 @163.com + * @since 2019 /1/23 + */ +public class BranchCommitResponseTest { + /** + * To string test. + */ + @Test + public void toStringTest() { + BranchCommitResponse branchCommitResponse = new BranchCommitResponse(); + branchCommitResponse.setXid("127.0.0.1:8091:123456"); + branchCommitResponse.setBranchId(2345678L); + branchCommitResponse.setBranchStatus(BranchStatus.PhaseOne_Done); + branchCommitResponse.setResultCode(ResultCode.Success); + branchCommitResponse.setMsg(""); + Assertions.assertEquals( + "xid=127.0.0.1:8091:123456,branchId=2345678,branchStatus=PhaseOne_Done,result code =Success,getMsg =", + branchCommitResponse.toString()); + + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/BranchRegisterRequestTest.java b/core/src/test/java/io/seata/core/message/BranchRegisterRequestTest.java new file mode 100644 index 0000000..9b86d09 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/BranchRegisterRequestTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.model.BranchType; +import io.seata.core.protocol.transaction.BranchRegisterRequest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch register request test. + */ +public class BranchRegisterRequestTest { + /** + * To string test. + */ + @Test + public void toStringTest() { + BranchRegisterRequest branchRegisterRequest = new BranchRegisterRequest(); + branchRegisterRequest.setXid("127.0.0.1:8091:1249853"); + branchRegisterRequest.setBranchType(BranchType.AT); + branchRegisterRequest.setResourceId("resource1"); + branchRegisterRequest.setLockKey("lock_key_1"); + Assertions.assertEquals("xid=127.0.0.1:8091:1249853,branchType=AT,resourceId=resource1,lockKey=lock_key_1", + branchRegisterRequest.toString()); + + } + +} diff --git a/core/src/test/java/io/seata/core/message/BranchRegisterResponseTest.java b/core/src/test/java/io/seata/core/message/BranchRegisterResponseTest.java new file mode 100644 index 0000000..ef431c2 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/BranchRegisterResponseTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.BranchRegisterResponse; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch register response test. + */ +public class BranchRegisterResponseTest { + + /** + * To string test. + * + * @throws Exception the exception + */ + @Test + public void toStringTest() { + BranchRegisterResponse branchRegisterResponse = new BranchRegisterResponse(); + branchRegisterResponse.setBranchId(123457L); + branchRegisterResponse.setResultCode(ResultCode.Success); + branchRegisterResponse.setMsg(""); + Assertions.assertEquals( + "BranchRegisterResponse: branchId=123457,result code =Success,getMsg =", + branchRegisterResponse.toString()); + + } +} diff --git a/core/src/test/java/io/seata/core/message/BranchReportRequestTest.java b/core/src/test/java/io/seata/core/message/BranchReportRequestTest.java new file mode 100644 index 0000000..8698863 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/BranchReportRequestTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.model.BranchStatus; +import io.seata.core.protocol.transaction.BranchReportRequest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch report request test. + */ +public class BranchReportRequestTest { + + /** + * Test to string. + */ + @Test + public void testToString() { + BranchReportRequest branchReportRequest = new BranchReportRequest(); + branchReportRequest.setXid("127.0.0.1:8091:1249853"); + branchReportRequest.setBranchId(3); + branchReportRequest.setResourceId("resource003"); + branchReportRequest.setStatus(BranchStatus.PhaseOne_Timeout); + branchReportRequest.setApplicationData("test app data"); + Assertions.assertEquals( + "xid=127.0.0.1:8091:1249853,branchId=3,resourceId=resource003,status=PhaseOne_Timeout," + + "applicationData=test app" + + " data", + branchReportRequest.toString()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/GlobalBeginRequestTest.java b/core/src/test/java/io/seata/core/message/GlobalBeginRequestTest.java new file mode 100644 index 0000000..3cc7d57 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/GlobalBeginRequestTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.protocol.transaction.GlobalBeginRequest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Global begin request test. + * + * @author xiajun.0706 @163.com + * @since 2019 /1/24 + */ +public class GlobalBeginRequestTest { + + /** + * Test to string. + * + * @throws Exception the exception + */ + @Test + public void testToString() throws Exception { + GlobalBeginRequest globalBeginRequest = new GlobalBeginRequest(); + globalBeginRequest.setTransactionName("tran 1"); + System.out.println(globalBeginRequest.toString()); + + Assertions.assertEquals("timeout=60000,transactionName=tran 1", globalBeginRequest.toString()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/GlobalCommitResponseTest.java b/core/src/test/java/io/seata/core/message/GlobalCommitResponseTest.java new file mode 100644 index 0000000..ad1a88a --- /dev/null +++ b/core/src/test/java/io/seata/core/message/GlobalCommitResponseTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.model.GlobalStatus; +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.GlobalCommitResponse; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Global commit response test. + * + * @author xiajun.0706 @163.com + * @since 2019 /1/24 + */ +public class GlobalCommitResponseTest { + + /** + * Test to string. + * + * @throws Exception the exception + */ + @Test + public void testToString() throws Exception { + GlobalCommitResponse globalCommitResponse = new GlobalCommitResponse(); + + globalCommitResponse.setGlobalStatus(GlobalStatus.Committed); + globalCommitResponse.setResultCode(ResultCode.Success); + globalCommitResponse.setMsg("OK"); + + System.out.println(globalCommitResponse.toString()); + + Assertions.assertEquals("globalStatus=Committed,ResultCode=Success,Msg=OK", globalCommitResponse.toString()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/GlobalRollbackRequestTest.java b/core/src/test/java/io/seata/core/message/GlobalRollbackRequestTest.java new file mode 100644 index 0000000..7081935 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/GlobalRollbackRequestTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.protocol.transaction.GlobalRollbackRequest; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Global rollback request test. + */ +public class GlobalRollbackRequestTest { + + /** + * Test to string. + */ + @Test + public void testToString() { + GlobalRollbackRequest globalRollbackRequest = new GlobalRollbackRequest(); + globalRollbackRequest.setXid("127.0.0.1:8091:1249853"); + globalRollbackRequest.setExtraData("test_extra_data"); + Assertions.assertEquals("xid=127.0.0.1:8091:1249853,extraData=test_extra_data", globalRollbackRequest.toString()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/message/RegisterTMRequestTest.java b/core/src/test/java/io/seata/core/message/RegisterTMRequestTest.java new file mode 100644 index 0000000..eae57ff --- /dev/null +++ b/core/src/test/java/io/seata/core/message/RegisterTMRequestTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.protocol.RegisterTMRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Register tm request test. + * + * @author linqiuping + */ +public class RegisterTMRequestTest { + + /** + * Test to string. + * + * @throws Exception the exception + */ + @Test + public void testToString() throws Exception { + RegisterTMRequest registerTMRequest = new RegisterTMRequest(); + registerTMRequest.setApplicationId("seata"); + registerTMRequest.setTransactionServiceGroup("daliy_2019"); + registerTMRequest.setVersion("2019-snapshot"); + Assertions.assertEquals("RegisterTMRequest{applicationId='seata', transactionServiceGroup='daliy_2019'}", + registerTMRequest.toString()); + } +} diff --git a/core/src/test/java/io/seata/core/message/RegisterTMResponseTest.java b/core/src/test/java/io/seata/core/message/RegisterTMResponseTest.java new file mode 100644 index 0000000..e546d10 --- /dev/null +++ b/core/src/test/java/io/seata/core/message/RegisterTMResponseTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.message; + +import io.seata.core.protocol.RegisterTMResponse; +import io.seata.core.protocol.ResultCode; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Register tm response test. + */ +public class RegisterTMResponseTest { + + /** + * Test to string. + * + * @throws Exception the exception + */ + @Test + public void testToString() throws Exception { + RegisterTMResponse registerTMResponse = new RegisterTMResponse(); + registerTMResponse.setVersion("1"); + registerTMResponse.setIdentified(true); + registerTMResponse.setResultCode(ResultCode.Success); + Assertions.assertEquals("version=1,extraData=null,identified=true,resultCode=Success,msg=null", + registerTMResponse.toString()); + } +} diff --git a/core/src/test/java/io/seata/core/model/BranchStatusTest.java b/core/src/test/java/io/seata/core/model/BranchStatusTest.java new file mode 100644 index 0000000..ccbaf9f --- /dev/null +++ b/core/src/test/java/io/seata/core/model/BranchStatusTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import io.seata.common.exception.ShouldNeverHappenException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * A unit test for {@link BranchStatus} + * + * @author Lay + */ +public class BranchStatusTest { + + private static final int REGISTERED_CODE = 1; + private static final int NONE = 99; + private static final int MIN_CODE = 0; + private static final int Max_CODE = 10; + + @Test + public void testGetCode() { + int code = BranchStatus.Registered.getCode(); + Assertions.assertEquals(code, REGISTERED_CODE); + } + + @Test + public void testGetWithByte() { + BranchStatus branchStatus = BranchStatus.get((byte) REGISTERED_CODE); + Assertions.assertEquals(branchStatus, BranchStatus.Registered); + } + + @Test + public void testGetWithInt() { + BranchStatus branchStatus = BranchStatus.get(REGISTERED_CODE); + Assertions.assertEquals(branchStatus, BranchStatus.Registered); + } + + @Test + public void testGetException() { + Assertions.assertThrows(ShouldNeverHappenException.class, () -> BranchStatus.get(NONE)); + } + + + @Test + public void testGetByCode() { + BranchStatus branchStatusOne = BranchStatus.get(MIN_CODE); + Assertions.assertEquals(branchStatusOne, BranchStatus.Unknown); + + BranchStatus branchStatusTwo = BranchStatus.get(Max_CODE); + Assertions.assertEquals(branchStatusTwo, BranchStatus.PhaseTwo_RollbackFailed_Unretryable); + + Assertions.assertThrows(ShouldNeverHappenException.class, () -> BranchStatus.get(NONE)); + } +} diff --git a/core/src/test/java/io/seata/core/model/BranchTypeTest.java b/core/src/test/java/io/seata/core/model/BranchTypeTest.java new file mode 100644 index 0000000..57111aa --- /dev/null +++ b/core/src/test/java/io/seata/core/model/BranchTypeTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * A unit test for {@link BranchType} + * + * @author Lay + */ +public class BranchTypeTest { + + private static final int AT_ORDINAL = 0; + private static final String AT_NAME = "AT"; + private static final int NONE = 99; + + @Test + public void testOrdinal() { + int ordinal = BranchType.AT.ordinal(); + Assertions.assertEquals(AT_ORDINAL, ordinal); + } + + @Test + public void testGetWithOrdinal() { + BranchType type = BranchType.get(BranchType.AT.ordinal()); + Assertions.assertEquals(type, BranchType.AT); + } + + @Test + public void testGetWithByte() { + BranchType type = BranchType.get((byte) AT_ORDINAL); + Assertions.assertEquals(type, BranchType.AT); + } + + @Test + public void testGetWithInt() { + BranchType type = BranchType.get(AT_ORDINAL); + Assertions.assertEquals(type, BranchType.AT); + } + + @Test + public void testGetWithName() { + BranchType type = BranchType.get(AT_NAME); + Assertions.assertEquals(type, BranchType.AT); + } + + @Test + public void testGetException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> BranchType.get(NONE)); + } + + +} diff --git a/core/src/test/java/io/seata/core/model/GlobalStatusTest.java b/core/src/test/java/io/seata/core/model/GlobalStatusTest.java new file mode 100644 index 0000000..12d2515 --- /dev/null +++ b/core/src/test/java/io/seata/core/model/GlobalStatusTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * A unit test for {@link GlobalStatus} + * + * @author Lay + */ +public class GlobalStatusTest { + private static final int BEGIN_CODE = 1; + private static final int NONE = 99; + private static final int MIN_CODE = 0; + private static final int MAX_CODE = 15; + + @Test + public void testGetCode() { + int code = GlobalStatus.Begin.getCode(); + Assertions.assertEquals(code, BEGIN_CODE); + } + + @Test + public void testGetWithByte() { + GlobalStatus branchStatus = GlobalStatus.get((byte) BEGIN_CODE); + Assertions.assertEquals(branchStatus, GlobalStatus.Begin); + } + + @Test + public void testGetWithInt() { + GlobalStatus branchStatus = GlobalStatus.get(BEGIN_CODE); + Assertions.assertEquals(branchStatus, GlobalStatus.Begin); + } + + @Test + public void testGetException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> GlobalStatus.get(NONE)); + } + + @Test + public void testGetByCode() { + GlobalStatus globalStatusOne = GlobalStatus.get(MIN_CODE); + Assertions.assertEquals(globalStatusOne, GlobalStatus.UnKnown); + + GlobalStatus globalStatusTwo = GlobalStatus.get(MAX_CODE); + Assertions.assertEquals(globalStatusTwo, GlobalStatus.Finished); + + Assertions.assertThrows(IllegalArgumentException.class, () -> GlobalStatus.get(NONE)); + } + +} diff --git a/core/src/test/java/io/seata/core/model/TransactionExceptionCodeTest.java b/core/src/test/java/io/seata/core/model/TransactionExceptionCodeTest.java new file mode 100644 index 0000000..80951eb --- /dev/null +++ b/core/src/test/java/io/seata/core/model/TransactionExceptionCodeTest.java @@ -0,0 +1,52 @@ +package io.seata.core.model; + +import io.seata.core.exception.TransactionExceptionCode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Montos + */ +public class TransactionExceptionCodeTest { + private static final int BEGIN_CODE = 1; + private static final int NONE = 99; + private static final int MIN_CODE = 0; + private static final int Max_CODE = 18; + + @Test + public void testGetCode() { + int code = TransactionExceptionCode.BeginFailed.ordinal(); + Assertions.assertEquals(code, BEGIN_CODE); + } + + @Test + public void testGetWithByte() { + TransactionExceptionCode branchStatus = TransactionExceptionCode.get((byte) BEGIN_CODE); + Assertions.assertEquals(branchStatus, TransactionExceptionCode.BeginFailed); + } + + @Test + public void testGetWithInt() { + TransactionExceptionCode branchStatus = TransactionExceptionCode.get(BEGIN_CODE); + Assertions.assertEquals(branchStatus, TransactionExceptionCode.BeginFailed); + } + + @Test + public void testGetException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> TransactionExceptionCode.get(NONE)); + } + + @Test + public void testGetByCode() { + TransactionExceptionCode transactionExceptionCodeOne = TransactionExceptionCode.get(MIN_CODE); + Assertions.assertEquals(transactionExceptionCodeOne, TransactionExceptionCode.Unknown); + + TransactionExceptionCode transactionExceptionCodeTwo = TransactionExceptionCode.get(Max_CODE); + Assertions.assertEquals(transactionExceptionCodeTwo, TransactionExceptionCode.FailedStore); + + Assertions.assertThrows(IllegalArgumentException.class, () -> TransactionExceptionCode.get(NONE)); + } + + + +} diff --git a/core/src/test/java/io/seata/core/protocol/MergeResultMessageTest.java b/core/src/test/java/io/seata/core/protocol/MergeResultMessageTest.java new file mode 100644 index 0000000..4fae571 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/MergeResultMessageTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.seata.core.protocol.transaction.GlobalBeginResponse; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type MergeResultMessage test. + * + * @author leizhiyuan + */ +public class MergeResultMessageTest { + + @Test + public void getAndSetMsgs() { + MergeResultMessage mergeResultMessage = new MergeResultMessage(); + final AbstractResultMessage[] msgs = new AbstractResultMessage[1]; + final GlobalBeginResponse globalBeginResponse = buildGlobalBeginResponse(); + msgs[0] = globalBeginResponse; + mergeResultMessage.setMsgs(msgs); + assertThat(globalBeginResponse).isEqualTo(mergeResultMessage.getMsgs()[0]); + } + + @Test + public void getTypeCode() { + MergeResultMessage mergeResultMessage = new MergeResultMessage(); + assertThat(MessageType.TYPE_SEATA_MERGE_RESULT).isEqualTo(mergeResultMessage.getTypeCode()); + } + + private GlobalBeginResponse buildGlobalBeginResponse() { + final GlobalBeginResponse globalBeginResponse = new GlobalBeginResponse(); + globalBeginResponse.setXid("xid"); + globalBeginResponse.setExtraData("data"); + globalBeginResponse.setMsg("success"); + globalBeginResponse.setResultCode(ResultCode.Success); + return globalBeginResponse; + } + +} diff --git a/core/src/test/java/io/seata/core/protocol/MergedWarpMessageTest.java b/core/src/test/java/io/seata/core/protocol/MergedWarpMessageTest.java new file mode 100644 index 0000000..4c44e03 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/MergedWarpMessageTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.seata.core.protocol.transaction.GlobalBeginRequest; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type MergedWarpMessageTest test. + * + * @author leizhiyuan + */ +public class MergedWarpMessageTest { + + @Test + public void getTypeCode() { + MergedWarpMessage mergedWarpMessage = new MergedWarpMessage(); + assertThat(mergedWarpMessage.getTypeCode()).isEqualTo(MessageType.TYPE_SEATA_MERGE); + } + + private GlobalBeginRequest buildGlobalBeginRequest() { + final GlobalBeginRequest globalBeginRequest = new GlobalBeginRequest(); + globalBeginRequest.setTransactionName("xx"); + globalBeginRequest.setTimeout(3000); + return globalBeginRequest; + } +} diff --git a/core/src/test/java/io/seata/core/protocol/MessageFutureTest.java b/core/src/test/java/io/seata/core/protocol/MessageFutureTest.java new file mode 100644 index 0000000..b7d3699 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/MessageFutureTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import com.alibaba.fastjson.JSON; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Message future test. + * + * @author guoyao + */ +public class MessageFutureTest { + + private static final String BODY_FIELD = "test_body"; + private static final int ID_FIELD = 100; + private static final byte CODEC_FIELD = 1; + private static final byte COMPRESS_FIELD = 2; + private static final byte MSG_TYPE_FIELD = 3; + private static final HashMap HEAD_FIELD = new HashMap<>(); + private static final long TIME_OUT_FIELD = 100L; + + /** + * Test field set get. + */ + @Test + public void testFieldSetGet() { + String fromJson = "{\n" + + "\t\"requestMessage\":{\n" + + "\t\t\"body\":\"" + BODY_FIELD + "\",\n" + + "\t\t\"codec\":" + CODEC_FIELD + ",\n" + + "\t\t\"compressor\":" + COMPRESS_FIELD + ",\n" + + "\t\t\"headMap\":" + HEAD_FIELD + ",\n" + + "\t\t\"id\":" + ID_FIELD + ",\n" + + "\t\t\"messageType\":" + MSG_TYPE_FIELD + "\n" + + "\t},\n" + + "\t\"timeout\":" + TIME_OUT_FIELD + "\n" + + "}"; + MessageFuture fromJsonFuture = JSON.parseObject(fromJson, MessageFuture.class); + assertThat(fromJsonFuture.getTimeout()).isEqualTo(TIME_OUT_FIELD); + MessageFuture toJsonFuture = new MessageFuture(); + toJsonFuture.setRequestMessage(buildRepcMessage()); + toJsonFuture.setTimeout(TIME_OUT_FIELD); + String toJson = JSON.toJSONString(toJsonFuture, true); + assertThat(toJson).isEqualTo(fromJson); + } + + /** + * Test is time out. + * + * @throws Exception the exception + */ + @Test + public void testIsTimeOut() throws Exception { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setTimeout(TIME_OUT_FIELD); + assertThat(messageFuture.isTimeout()).isFalse(); + Thread.sleep(TIME_OUT_FIELD + 1); + assertThat(messageFuture.isTimeout()).isTrue(); + + } + + /** + * Test get no result with time out exception. + */ + @Test + public void testGetNoResultWithTimeOutException() { + Assertions.assertThrows(TimeoutException.class, () -> { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + }); + } + + /** + * Test get has result with time out exception. + */ + @Test + public void testGetHasResultWithTimeOutException() { + Assertions.assertThrows(TimeoutException.class, () -> { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + CountDownLatch downLatch = new CountDownLatch(1); + executorService.execute(() -> { + try { + downLatch.await(); + messageFuture.setResultMessage("has_result"); + } catch (InterruptedException e) { + + } + }); + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + downLatch.countDown(); + }); + } + + /** + * Test get has result with run time exception. + */ + @Test + public void testGetHasResultWithRunTimeException() { + Assertions.assertThrows(RuntimeException.class, () -> { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + messageFuture.setResultMessage(new RuntimeException()); + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + }); + } + + /** + * Test get has result with run time exception with message. + * + * @throws Exception the exception + */ + @Test + public void testGetHasResultWithRunTimeExceptionWithMessage() throws Exception { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + RuntimeException runtimeException = new RuntimeException("test_runtime"); + messageFuture.setResultMessage(runtimeException); + try { + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + } catch (Exception e) { + assertThat(e.getMessage()).isEqualTo(runtimeException.getMessage()); + } + } + + /** + * Test get has result with throwable. + */ + @Test + public void testGetHasResultWithThrowable() { + Assertions.assertThrows(RuntimeException.class, () -> { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + messageFuture.setResultMessage(new Throwable()); + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + }); + } + + /** + * Test get has result with throwable with message. + * + * @throws Exception the exception + */ + @Test + public void testGetHasResultWithThrowableWithMessage() throws Exception { + MessageFuture messageFuture = new MessageFuture(); + messageFuture.setRequestMessage(buildRepcMessage()); + messageFuture.setTimeout(TIME_OUT_FIELD); + Throwable throwable = new Throwable("test_throwable"); + messageFuture.setResultMessage(throwable); + try { + messageFuture.get(TIME_OUT_FIELD, TimeUnit.MILLISECONDS); + } catch (Exception e) { + assertThat(e.getMessage()).isEqualTo(new RuntimeException(throwable).getMessage()); + } + } + + private RpcMessage buildRepcMessage() { + RpcMessage rpcMessage = new RpcMessage(); + rpcMessage.setId(ID_FIELD); + rpcMessage.setMessageType(MSG_TYPE_FIELD); + rpcMessage.setCodec(CODEC_FIELD); + rpcMessage.setCompressor(COMPRESS_FIELD); + rpcMessage.setBody(BODY_FIELD); + return rpcMessage; + } +} diff --git a/core/src/test/java/io/seata/core/protocol/RegisterRMRequestTest.java b/core/src/test/java/io/seata/core/protocol/RegisterRMRequestTest.java new file mode 100644 index 0000000..2c0d4c5 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/RegisterRMRequestTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * The type RegisterRMRequest test. + * + * @author leizhiyuan + */ +public class RegisterRMRequestTest { + + @Test + public void getAndSetResourceIds() { + RegisterRMRequest registerRMRequest = new RegisterRMRequest(); + final String resourceIds = "r1,r2"; + registerRMRequest.setResourceIds(resourceIds); + assertThat(resourceIds).isEqualTo(registerRMRequest.getResourceIds()); + } + + @Test + public void getTypeCode() { + RegisterRMRequest registerRMRequest = new RegisterRMRequest(); + assertThat(MessageType.TYPE_REG_RM).isEqualTo(registerRMRequest.getTypeCode()); + } + + private RegisterRMRequest buildRegisterRMRequest() { + + RegisterRMRequest registerRMRequest = new RegisterRMRequest(); + final String resourceIds = "r1,r2"; + registerRMRequest.setResourceIds(resourceIds); + registerRMRequest.setApplicationId("app"); + registerRMRequest.setExtraData("extra"); + registerRMRequest.setTransactionServiceGroup("group"); + registerRMRequest.setVersion("1"); + return registerRMRequest; + } +} diff --git a/core/src/test/java/io/seata/core/protocol/RegisterTMRequestTest.java b/core/src/test/java/io/seata/core/protocol/RegisterTMRequestTest.java new file mode 100644 index 0000000..198c1ac --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/RegisterTMRequestTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import io.netty.buffer.ByteBuf; +import static io.netty.buffer.Unpooled.buffer; + +/** + * RegisterTMRequest Test + * + * @author kaitithoma + * @author Danaykap + * + * + */ + +public class RegisterTMRequestTest { + + private static RegisterTMRequest registerTMRequest; + private static AbstractIdentifyRequest air; + private static final String APP_ID = "applicationId"; + private static final String TSG = "transactionServiceGroup"; + private static final String ED = "extraData"; + private static final short TYPE_CODE = 101; + private static final ByteBuf BB = buffer(128); + + + +} diff --git a/core/src/test/java/io/seata/core/protocol/RpcMessageTest.java b/core/src/test/java/io/seata/core/protocol/RpcMessageTest.java new file mode 100644 index 0000000..0eb83a8 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/RpcMessageTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol; + +import com.alibaba.fastjson.JSON; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The type Rpc message test. + * + * @author guoyao + */ +public class RpcMessageTest { + + private static final String BODY_FIELD = "test_body"; + private static final int ID_FIELD = 100; + private static final byte CODEC_FIELD = 1; + private static final byte COMPRESS_FIELD = 2; + private static final byte MSG_TYPE_FIELD = 3; + private static final HashMap HEAD_FIELD = new HashMap<>(); + + /** + * Test field get set from json. + */ + @Test + public void testFieldGetSetFromJson() { + String fromJson = "{\n" + + "\t\"body\":\"" + BODY_FIELD + "\",\n" + + "\t\"codec\":" + CODEC_FIELD + ",\n" + + "\t\"compressor\":" + COMPRESS_FIELD + ",\n" + + "\t\"headMap\":" + HEAD_FIELD + ",\n" + + "\t\"id\":" + ID_FIELD + ",\n" + + "\t\"messageType\":" + MSG_TYPE_FIELD + "\n" + + "}"; + RpcMessage fromJsonMessage = JSON.parseObject(fromJson, RpcMessage.class); + assertThat(fromJsonMessage.getBody()).isEqualTo(BODY_FIELD); + assertThat(fromJsonMessage.getId()).isEqualTo(ID_FIELD); + + RpcMessage toJsonMessage = new RpcMessage(); + toJsonMessage.setBody(BODY_FIELD); + toJsonMessage.setId(ID_FIELD); + toJsonMessage.setMessageType(MSG_TYPE_FIELD); + toJsonMessage.setCodec(CODEC_FIELD); + toJsonMessage.setCompressor(COMPRESS_FIELD); + toJsonMessage.setHeadMap(HEAD_FIELD); + String toJson = JSON.toJSONString(toJsonMessage, true); + assertThat(fromJson).isEqualTo(toJson); + } +} diff --git a/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackRequestTest.java b/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackRequestTest.java new file mode 100644 index 0000000..eda94ba --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackRequestTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + + +import io.seata.core.model.BranchType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author: slievrly + */ +public class BranchRollbackRequestTest { + @Test + public void toStringTest() { + BranchRollbackRequest branchRollbackRequest = new BranchRollbackRequest(); + + branchRollbackRequest.setXid("127.0.0.1:9999:39875642"); + branchRollbackRequest.setBranchId(1); + branchRollbackRequest.setBranchType(BranchType.AT); + branchRollbackRequest.setResourceId("resource1"); + branchRollbackRequest.setApplicationData("app1"); + + Assertions.assertEquals("xid=127.0.0.1:9999:39875642,branchId=1,branchType=AT," + + "resourceId=resource1,applicationData=app1", branchRollbackRequest.toString()); + + } + +} diff --git a/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackResponseTest.java b/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackResponseTest.java new file mode 100644 index 0000000..5de4e35 --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/transaction/BranchRollbackResponseTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.model.BranchStatus; +import io.seata.core.protocol.ResultCode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author: slievrly + */ +public class BranchRollbackResponseTest { + @Test + public void toStringTest() { + BranchRollbackResponse branchRollbackResponse = new BranchRollbackResponse(); + branchRollbackResponse.setXid("127.0.0.1:8091:123456"); + branchRollbackResponse.setBranchId(2345678L); + branchRollbackResponse.setBranchStatus(BranchStatus.PhaseOne_Done); + branchRollbackResponse.setResultCode(ResultCode.Success); + branchRollbackResponse.setMsg(""); + Assertions.assertEquals( + "xid=127.0.0.1:8091:123456,branchId=2345678,branchStatus=PhaseOne_Done,result code =Success,getMsg =", + branchRollbackResponse.toString()); + + } + +} diff --git a/core/src/test/java/io/seata/core/protocol/transaction/GlobalBeginResponseTest.java b/core/src/test/java/io/seata/core/protocol/transaction/GlobalBeginResponseTest.java new file mode 100644 index 0000000..022aeda --- /dev/null +++ b/core/src/test/java/io/seata/core/protocol/transaction/GlobalBeginResponseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.protocol.transaction; + +import io.seata.core.protocol.MessageType; +import io.seata.core.protocol.ResultCode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * A unit test for {@link GlobalBeginResponse} + * + * @author liujc + **/ +public class GlobalBeginResponseTest { + private final String XID = "test_xid"; + private final String EXTRA_DATA = "test_extra_data"; + private final ResultCode RESULT_CODE = ResultCode.Success; + + @Test + public void testGetSetXid() { + GlobalBeginResponse globalBeginResponse = new GlobalBeginResponse(); + globalBeginResponse.setXid(XID); + Assertions.assertEquals(XID, globalBeginResponse.getXid()); + } + + @Test + public void testGetSetExtraData() { + GlobalBeginResponse globalBeginResponse = new GlobalBeginResponse(); + globalBeginResponse.setExtraData(EXTRA_DATA); + Assertions.assertEquals(EXTRA_DATA, globalBeginResponse.getExtraData()); + } + + @Test + public void testGetTypeCode() { + GlobalBeginResponse globalBeginResponse = new GlobalBeginResponse(); + Assertions.assertEquals(MessageType.TYPE_GLOBAL_BEGIN_RESULT, globalBeginResponse.getTypeCode()); + } + +} diff --git a/core/src/test/java/io/seata/core/rpc/RpcContextTest.java b/core/src/test/java/io/seata/core/rpc/RpcContextTest.java new file mode 100644 index 0000000..8f277a9 --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/RpcContextTest.java @@ -0,0 +1,185 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.util.HashSet; + +/** + * RpcContext Test + * + * @author kaitithoma + * @author Danaykap + * + * + */ + +public class RpcContextTest { + /** RpcContext constructor parameter set as constant **/ + private static RpcContext rpcContext; + /** Version value **/ + private static final String VERSION = "a"; + /** TransactionServiceGroup value **/ + private static final String TSG = "a"; + /** ID value for every method needing an Id **/ + private static final String ID = "1"; + /** ResourceValue value **/ + private static final String RV = "abc"; + /** ResourceSet value **/ + private static final String RS = "b"; + + /** + * RpcContext Constructor + */ + + @BeforeAll + public static void setup() { + rpcContext = new RpcContext(); + } + + /** + * Test set ApplicationId to value = "1" Test get ApplicationId + */ + @Test + public void testApplicationIdValue() { + rpcContext.setApplicationId(ID); + Assertions.assertEquals(ID, rpcContext.getApplicationId()); + } + + /** + * Test set Version to value = "a" Test get Version + */ + @Test + public void testVersionValue() { + rpcContext.setVersion(VERSION); + Assertions.assertEquals(VERSION, rpcContext.getVersion()); + } + + /** + * Test set ClientId to value = "1" Test get ClientId + */ + @Test + public void testClientIdValue() { + rpcContext.setClientId(ID); + Assertions.assertEquals(ID, rpcContext.getClientId()); + } + + /** + * Test set Channel to null Test get Channel + */ + @Test + public void testChannelNull() { + rpcContext.setChannel(null); + Assertions.assertNull(rpcContext.getChannel()); + } + + /** + * Test set TransactionServiceGroup to value = "1" Test get + * TransactionServiceGroup + */ + @Test + public void testTransactionServiceGroupValue() { + rpcContext.setTransactionServiceGroup(TSG); + Assertions.assertEquals(TSG, rpcContext.getTransactionServiceGroup()); + } + + /** + * Test setClientRole to null Test getApplication Id + */ + @Test + public void testClientRoleNull() { + rpcContext.setClientRole(null); + Assertions.assertNull(rpcContext.getClientRole()); + } + + /** + * Test set ResourceSets to null Test get ResourceSets + */ + @Test + public void testResourceSetsNull() { + rpcContext.setResourceSets(null); + Assertions.assertNull(rpcContext.getResourceSets()); + } + + /** + * Test add resourceSet = null with addResource Test get ResourceSets + */ + @Test + public void testAddResourceNull() { + HashSet resourceSet = new HashSet(); + rpcContext.setResourceSets(resourceSet); + rpcContext.addResource(null); + Assertions.assertEquals(0, rpcContext.getResourceSets().size()); + } + + /** + * Test add null parameter to ResourceSets with addResources Test get + * ResourceSets + */ + @Test + public void testAddResourcesNull() { + rpcContext.addResources(null); + rpcContext.setResourceSets(null); + Assertions.assertNull(rpcContext.getResourceSets()); + } + + /** + * Test add a short resourceSet(["abc"]) with addResources Test get ResourceSets + */ + @Test + public void testAddResourcesResourceValue() { + HashSet resourceSet = new HashSet(); + resourceSet.add(RV); + rpcContext.addResources(resourceSet); + Assertions.assertEquals(resourceSet, rpcContext.getResourceSets()); + } + + /** + * Test add resource and resource sets to ResourceSets with addResourceSets Test + * getResourceSets + */ + @Test + public void testAddResourcesResourceSetValue() { + HashSet resourceSets = new HashSet(); + resourceSets.add(RS); + HashSet resourceSet = new HashSet(); + resourceSet.add(RV); + rpcContext.addResources(resourceSet); + rpcContext.setResourceSets(resourceSets); + rpcContext.addResources(resourceSet); + Assertions.assertEquals(resourceSets, rpcContext.getResourceSets()); + } + + /** + * Test toString having all the parameters initialized to null + */ + @Test + public void testToString() { + rpcContext.setApplicationId(null); + rpcContext.setTransactionServiceGroup(null); + rpcContext.setClientId(null); + rpcContext.setChannel(null); + rpcContext.setResourceSets(null); + Assertions.assertEquals( + "RpcContext{" + "applicationId='" + rpcContext.getApplicationId() + '\'' + ", transactionServiceGroup='" + + rpcContext.getTransactionServiceGroup() + '\'' + ", clientId='" + rpcContext.getClientId() + '\'' + + ", channel=" + rpcContext.getChannel() + ", resourceSets=" + rpcContext.getResourceSets() + '}', + rpcContext.toString()); + } + +} diff --git a/core/src/test/java/io/seata/core/rpc/ShutdownHookTest.java b/core/src/test/java/io/seata/core/rpc/ShutdownHookTest.java new file mode 100644 index 0000000..c5244cc --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/ShutdownHookTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.spy; + +public class ShutdownHookTest { + + private int previousPriority = -1; + + private final ShutdownHook hook = ShutdownHook.getInstance(); + + private final Random random = new Random(); + + @BeforeAll + static void beforeAll() { + ShutdownHook.removeRuntimeShutdownHook(); + } + + @Test + void testAddAndExecute() throws InterruptedException { + // note: all of them had been added in the addDisposable method + List disposableList = getRandomDisposableList(); + + hook.start(); + hook.join(); + + disposableList.forEach(disposable -> verify(disposable, times(1)).destroy()); + } + + private List getRandomDisposableList() { + return IntStream.rangeClosed(0, 10) + .boxed() + .flatMap(this::generateDisposableStream) + .collect(Collectors.toList()); + } + + private Stream generateDisposableStream(int priority) { + int size = random.nextInt(10); + return IntStream.range(0, size).mapToObj(i -> addDisposable(priority)); + } + + private Disposable addDisposable(int priority) { + Disposable disposable = new TestDisposable(priority); + Disposable wrapper = spy(disposable); + hook.addDisposable(wrapper, priority); + return wrapper; + } + + private class TestDisposable implements Disposable { + + private final int priority; + + @Override + public void destroy() { + assertTrue(previousPriority <= priority, "lower priority should be executed first"); + previousPriority = priority; + } + + public TestDisposable(int priority) { + this.priority = priority; + } + } +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/rpc/netty/MessageCodecHandlerTest.java b/core/src/test/java/io/seata/core/rpc/netty/MessageCodecHandlerTest.java new file mode 100644 index 0000000..5feeb6a --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/MessageCodecHandlerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +///* +// * Copyright 1999-2019 Seata.io Group. +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// */ +//package io.seata.core.rpc.netty; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import io.netty.buffer.ByteBuf; +//import io.netty.buffer.UnpooledByteBufAllocator; +//import io.netty.channel.ChannelHandlerContext; +//import io.seata.core.protocol.RpcMessage; +//import io.seata.core.protocol.transaction.GlobalBeginRequest; +//import org.junit.Ignore; +//import org.junit.jupiter.api.Test; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.assertj.core.api.Assertions.fail; +// +//public class MessageCodecHandlerTest { +// +// @Test +// @Ignore("seata serialize can not support this way,only we can change serialize type dynamicly.") +// public void encodeAndDecode() { +// +// MessageCodecHandler messageCodecHandler = new MessageCodecHandler(); +// ByteBuf out = UnpooledByteBufAllocator.DEFAULT.directBuffer(1024); +// +// GlobalBeginRequest globalBeginRequest = new GlobalBeginRequest(); +// globalBeginRequest.setTransactionName("trans-1"); +// globalBeginRequest.setTimeout(3000); +// +// RpcMessage msg = new RpcMessage(); +// msg.setId(1); +// msg.setAsync(false); +// msg.setHeartbeat(false); +// msg.setRequest(true); +// msg.setBody(globalBeginRequest); +// ChannelHandlerContext ctx = null; +// try { +// messageCodecHandler.encode(ctx, msg, out); +// } catch (Exception e) { +// fail(e.getMessage()); +// } +// List objetcs = new ArrayList<>(); +// try { +// messageCodecHandler.decode(ctx, out, objetcs); +// } catch (Exception e) { +// fail(e.getMessage()); +// } +// +// assertThat(objetcs.size()).isEqualTo(1); +// final Object actual = objetcs.get(0); +// assertThat(actual instanceof RpcMessage).isEqualTo(true); +// +// RpcMessage rpcMessage = (RpcMessage)actual; +// +// GlobalBeginRequest decodeGlobalBeginRequest = (GlobalBeginRequest)rpcMessage.getBody(); +// assertThat(decodeGlobalBeginRequest.getTransactionName()).isEqualTo( +// globalBeginRequest.getTransactionName()); +// assertThat(decodeGlobalBeginRequest.getTimeout()).isEqualTo(globalBeginRequest.getTimeout()); +// assertThat(decodeGlobalBeginRequest.getTypeCode()).isEqualTo(globalBeginRequest.getTypeCode()); +// +// } +// +//} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/rpc/netty/NettyBaseConfigTest.java b/core/src/test/java/io/seata/core/rpc/netty/NettyBaseConfigTest.java new file mode 100644 index 0000000..71eb325 --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/NettyBaseConfigTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Netty base config test. + * + * @author slievrly + * @author wang.liang + */ +class NettyBaseConfigTest { + /** + * Name. + */ + @Test + void name() { + NettyBaseConfig nettyBaseConfig = new NettyBaseConfig(); + System.out.print("test static ."); + } + + @Test + void test_enum_WorkThreadMode_getModeByName() { + for (NettyBaseConfig.WorkThreadMode value : NettyBaseConfig.WorkThreadMode.values()) { + Assertions.assertEquals(NettyBaseConfig.WorkThreadMode.getModeByName(value.name().toLowerCase()), value); + } + Assertions.assertNull(NettyBaseConfig.WorkThreadMode.getModeByName(null)); + Assertions.assertNull(NettyBaseConfig.WorkThreadMode.getModeByName("null")); + } +} diff --git a/core/src/test/java/io/seata/core/rpc/netty/NettyClientChannelManagerTest.java b/core/src/test/java/io/seata/core/rpc/netty/NettyClientChannelManagerTest.java new file mode 100644 index 0000000..eb34f7a --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/NettyClientChannelManagerTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.channel.Channel; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Field; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Netty client channel manager test. + * + * @author zhaojun + */ +@ExtendWith(MockitoExtension.class) +class NettyClientChannelManagerTest { + + private NettyClientChannelManager channelManager; + + @Mock + private NettyPoolableFactory poolableFactory; + + @Mock + private Function poolKeyFunction; + + private NettyClientConfig nettyClientConfig = new NettyClientConfig(); + + @Mock + private NettyPoolKey nettyPoolKey; + + @Mock + private Channel channel; + + @Mock + private Channel newChannel; + + @Mock + private GenericKeyedObjectPool keyedObjectPool; + + @BeforeEach + void setUp() { + channelManager = new NettyClientChannelManager(poolableFactory, poolKeyFunction, nettyClientConfig); + } + + @AfterEach + void tearDown() { + } + + @Test + void assertAcquireChannelFromPool() { + setupPoolFactory(nettyPoolKey, channel); + Channel actual = channelManager.acquireChannel("localhost"); + verify(poolableFactory).makeObject(nettyPoolKey); + Assertions.assertEquals(actual, channel); + } + + private void setupPoolFactory(final NettyPoolKey nettyPoolKey, final Channel channel) { + when(poolKeyFunction.apply(anyString())).thenReturn(nettyPoolKey); + when(poolableFactory.makeObject(nettyPoolKey)).thenReturn(channel); + when(poolableFactory.validateObject(nettyPoolKey, channel)).thenReturn(true); + } + + @Test + void assertAcquireChannelFromCache() { + channelManager.getChannels().putIfAbsent("localhost", channel); + when(channel.isActive()).thenReturn(true); + Channel actual = channelManager.acquireChannel("localhost"); + verify(poolableFactory, times(0)).makeObject(nettyPoolKey); + Assertions.assertEquals(actual, channel); + } + + @Test + void assertAcquireChannelFromPoolContainsInactiveCache() { + channelManager.getChannels().putIfAbsent("localhost", channel); + when(channel.isActive()).thenReturn(false); + setupPoolFactory(nettyPoolKey, newChannel); + Channel actual = channelManager.acquireChannel("localhost"); + verify(poolableFactory).makeObject(nettyPoolKey); + Assertions.assertEquals(actual, newChannel); + } + + @Test + void assertReconnect() { + channelManager.getChannels().putIfAbsent("127.0.0.1:8091", channel); + when(channel.isActive()).thenReturn(true); + channelManager.reconnect("my_test_tx_group"); + verify(channel).isActive(); + } + + @Test + @SuppressWarnings("unchecked") + void assertReleaseChannelWhichCacheIsEmpty() throws Exception { + setNettyClientKeyPool(); + setUpReleaseChannel(); + channelManager.releaseChannel(channel, "127.0.0.1:8091"); + verify(keyedObjectPool).returnObject(nettyPoolKey, channel); + } + + @Test + @SuppressWarnings("unchecked") + void assertReleaseCachedChannel() throws Exception { + setNettyClientKeyPool(); + setUpReleaseChannel(); + channelManager.getChannels().putIfAbsent("127.0.0.1:8091", channel); + channelManager.releaseChannel(channel, "127.0.0.1:8091"); + assertTrue(channelManager.getChannels().isEmpty()); + verify(keyedObjectPool).returnObject(nettyPoolKey, channel); + } + + @Test + @SuppressWarnings("unchecked") + void assertReleaseChannelNotEqualToCache() throws Exception { + setNettyClientKeyPool(); + setUpReleaseChannel(); + channelManager.getChannels().putIfAbsent("127.0.0.1:8091", newChannel); + channelManager.releaseChannel(channel, "127.0.0.1:8091"); + assertEquals(1, channelManager.getChannels().size()); + verify(keyedObjectPool).returnObject(nettyPoolKey, channel); + } + + @SuppressWarnings("unchecked") + private void setUpReleaseChannel() { + ConcurrentMap channelLocks = + (ConcurrentMap) getFieldValue("channelLocks", channelManager); + channelLocks.putIfAbsent("127.0.0.1:8091", new Object()); + ConcurrentMap poolKeyMap = + (ConcurrentMap) getFieldValue("poolKeyMap", channelManager); + poolKeyMap.putIfAbsent("127.0.0.1:8091", nettyPoolKey); + } + + private Object getFieldValue(final String fieldName, final Object targetObject) { + try { + Field field = targetObject.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(targetObject); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @SuppressWarnings("unchecked") + private void setNettyClientKeyPool() { + try { + Field field = channelManager.getClass().getDeclaredField("nettyClientKeyPool"); + field.setAccessible(true); + field.set(channelManager, keyedObjectPool); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/rpc/netty/NettyPoolKeyTest.java b/core/src/test/java/io/seata/core/rpc/netty/NettyPoolKeyTest.java new file mode 100644 index 0000000..1279434 --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/NettyPoolKeyTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.seata.core.protocol.RegisterRMRequest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author: slievrly + */ +public class NettyPoolKeyTest { + + private NettyPoolKey nettyPoolKey; + private static final NettyPoolKey.TransactionRole RM_ROLE = NettyPoolKey.TransactionRole.RMROLE; + private static final NettyPoolKey.TransactionRole TM_ROLE = NettyPoolKey.TransactionRole.TMROLE; + private static final String ADDRESS1 = "127.0.0.1:8091"; + private static final String ADDRESS2 = "127.0.0.1:8092"; + private static final RegisterRMRequest MSG1 = new RegisterRMRequest("applicationId1", "transactionServiceGroup1"); + private static final RegisterRMRequest MSG2 = new RegisterRMRequest("applicationId2", "transactionServiceGroup2"); + + @BeforeEach + public void init() { + nettyPoolKey = new NettyPoolKey(RM_ROLE, ADDRESS1, MSG1); + } + + @Test + public void getTransactionRole() { + Assertions.assertEquals(nettyPoolKey.getTransactionRole(), RM_ROLE); + } + + @Test + public void setTransactionRole() { + nettyPoolKey.setTransactionRole(TM_ROLE); + Assertions.assertEquals(nettyPoolKey.getTransactionRole(), TM_ROLE); + } + + @Test + public void getAddress() { + Assertions.assertEquals(nettyPoolKey.getAddress(), ADDRESS1); + } + + @Test + public void setAddress() { + nettyPoolKey.setAddress(ADDRESS2); + Assertions.assertEquals(nettyPoolKey.getAddress(), ADDRESS2); + } + + @Test + public void getMessage() { + Assertions.assertEquals(nettyPoolKey.getMessage(), MSG1); + } + + @Test + public void setMessage() { + nettyPoolKey.setMessage(MSG2); + Assertions.assertEquals(nettyPoolKey.getMessage(), MSG2); + } + + @Test + public void testToString() { + String expectStr = "transactionRole:RMROLE,address:127.0.0.1:8091,msg:< " + MSG1.toString() + " >"; + Assertions.assertEquals(nettyPoolKey.toString(), expectStr); + } +} diff --git a/core/src/test/java/io/seata/core/rpc/netty/RmNettyClientTest.java b/core/src/test/java/io/seata/core/rpc/netty/RmNettyClientTest.java new file mode 100644 index 0000000..e87d38b --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/RmNettyClientTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Rm RPC client test. + * + * @author zhaojun + */ +class RmNettyClientTest { + + @Test + public void assertGetInstanceAfterDestroy() { + RmNettyRemotingClient oldClient = RmNettyRemotingClient.getInstance("ap", "group"); + AtomicBoolean initialized = getInitializeStatus(oldClient); + oldClient.init(); + assertTrue(initialized.get()); + oldClient.destroy(); + assertFalse(initialized.get()); + RmNettyRemotingClient newClient = RmNettyRemotingClient.getInstance("ap", "group"); + Assertions.assertNotEquals(oldClient, newClient); + initialized = getInitializeStatus(newClient); + assertFalse(initialized.get()); + newClient.init(); + assertTrue(initialized.get()); + newClient.destroy(); + } + + private AtomicBoolean getInitializeStatus(final RmNettyRemotingClient rmNettyRemotingClient) { + try { + Field field = rmNettyRemotingClient.getClass().getDeclaredField("initialized"); + field.setAccessible(true); + return (AtomicBoolean) field.get(rmNettyRemotingClient); + } catch (Exception ex) { + throw new RuntimeException(ex.getMessage()); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java b/core/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java new file mode 100644 index 0000000..35cb14a --- /dev/null +++ b/core/src/test/java/io/seata/core/rpc/netty/TmNettyClientTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.rpc.netty; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelOption; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * The type Tm rpc client test. + * + * @author slievrly xiajun.0706@163.com + */ +public class TmNettyClientTest { + + private static final ThreadPoolExecutor + workingThreads = new ThreadPoolExecutor(100, 500, 500, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(20000), new ThreadPoolExecutor.CallerRunsPolicy()); + + /** + * Test get instance. + * + * @throws Exception the exceptionDataSourceManager. + */ + @Test + public void testGetInstance() throws Exception { + String applicationId = "app 1"; + String transactionServiceGroup = "group A"; + TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); + Field nettyClientKeyPoolField = getDeclaredField(tmNettyRemotingClient.getClientChannelManager(), "nettyClientKeyPool"); + nettyClientKeyPoolField.setAccessible(true); + GenericKeyedObjectPool nettyClientKeyPool = (GenericKeyedObjectPool) nettyClientKeyPoolField.get(tmNettyRemotingClient.getClientChannelManager()); + NettyClientConfig defaultNettyClientConfig = new NettyClientConfig(); + Assertions.assertEquals(defaultNettyClientConfig.getMaxPoolActive(), nettyClientKeyPool.getMaxActive()); + Assertions.assertEquals(defaultNettyClientConfig.getMinPoolIdle(), nettyClientKeyPool.getMinIdle()); + Assertions.assertEquals(defaultNettyClientConfig.getMaxAcquireConnMills(), nettyClientKeyPool.getMaxWait()); + Assertions.assertEquals(defaultNettyClientConfig.isPoolTestBorrow(), nettyClientKeyPool.getTestOnBorrow()); + Assertions.assertEquals(defaultNettyClientConfig.isPoolTestReturn(), nettyClientKeyPool.getTestOnReturn()); + Assertions.assertEquals(defaultNettyClientConfig.isPoolLifo(), nettyClientKeyPool.getLifo()); + } + + /** + * Do connect. + * + * @throws Exception the exception + */ + @Test + public void testInit() throws Exception { + String applicationId = "app 1"; + String transactionServiceGroup = "group A"; + TmNettyRemotingClient tmNettyRemotingClient = TmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); + + tmNettyRemotingClient.init(); + + //check if attr of tmNettyClient object has been set success + Field clientBootstrapField = getDeclaredField(tmNettyRemotingClient, "clientBootstrap"); + clientBootstrapField.setAccessible(true); + NettyClientBootstrap clientBootstrap = (NettyClientBootstrap)clientBootstrapField.get(tmNettyRemotingClient); + Field bootstrapField = getDeclaredField(clientBootstrap, "bootstrap"); + bootstrapField.setAccessible(true); + Bootstrap bootstrap = (Bootstrap) bootstrapField.get(clientBootstrap); + + Assertions.assertNotNull(bootstrap); + Field optionsField = getDeclaredField(bootstrap, "options"); + optionsField.setAccessible(true); + Map, Object> options = (Map, Object>)optionsField.get(bootstrap); + Assertions.assertTrue(Boolean.TRUE.equals(options.get(ChannelOption.TCP_NODELAY))); + Assertions.assertTrue(Boolean.TRUE.equals(options.get(ChannelOption.SO_KEEPALIVE))); + Assertions.assertEquals(10000, options.get(ChannelOption.CONNECT_TIMEOUT_MILLIS)); + Assertions.assertTrue(Boolean.TRUE.equals(options.get(ChannelOption.SO_KEEPALIVE))); + Assertions.assertEquals(153600, options.get(ChannelOption.SO_RCVBUF)); + + Field channelFactoryField = getDeclaredField(bootstrap, "channelFactory"); + channelFactoryField.setAccessible(true); + ChannelFactory + channelFactory = (ChannelFactory)channelFactoryField.get(bootstrap); + Assertions.assertNotNull(channelFactory); + Assertions.assertTrue(channelFactory.newChannel() instanceof NioSocketChannel); + + } + + /** + * Gets application id. + * + * @throws Exception the exception + */ + @Test + public void getApplicationId() throws Exception { + + } + + /** + * Sets application id. + * + * @throws Exception the exception + */ + @Test + public void setApplicationId() throws Exception { + + } + + /** + * get private field in parent class + * + * @param object the object + * @param fieldName the field name + * @return declared field + */ + public static Field getDeclaredField(Object object, String fieldName) { + Field field = null; + Class clazz = object.getClass(); + for (; clazz != Object.class; clazz = clazz.getSuperclass()) { + try { + field = clazz.getDeclaredField(fieldName); + return field; + } catch (Exception e) { + + } + } + + return null; + } +} diff --git a/core/src/test/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java b/core/src/test/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java new file mode 100644 index 0000000..5dfa026 --- /dev/null +++ b/core/src/test/java/io/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java @@ -0,0 +1,273 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.lock; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the Lock Store Sql Factory Test + * + * @author zhangchenghui.dev@gmail.com + * @since 1.2.0 + */ +public class LockStoreSqlFactoryTest { + + private static LockStoreSql MYSQL_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("mysql"); + + private static LockStoreSql ORACLE_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("oracle"); + + private static LockStoreSql POSTGRESQL_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("postgresql"); + + private static LockStoreSql H2_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("h2"); + + private static LockStoreSql OCEANBASE_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("oceanbase"); + + private static String GLOBAL_TABLE = "global_table"; + + private static String BRANCH_TABLE = "branch_table"; + + @Test + public void mysqlLockTest() { + String sql; + // Get insert lock sql string. + sql = MYSQL_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = MYSQL_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSqlByBranch(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSqlByBranch(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get query lock sql string. + sql = MYSQL_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = MYSQL_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = MYSQL_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + } + + @Test + public void oracleLockTest() { + String sql; + // Get insert lock sql string. + sql = ORACLE_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = ORACLE_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSqlByBranch(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSqlByBranch(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get query lock sql string. + sql = ORACLE_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = ORACLE_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = ORACLE_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + } + + @Test + public void pgLockTest() { + String sql; + // Get insert lock sql string. + sql = POSTGRESQL_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = POSTGRESQL_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSqlByBranch(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSqlByBranch(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get query lock sql string. + sql = POSTGRESQL_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = POSTGRESQL_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = POSTGRESQL_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + } + + @Test + public void h2LockTest() { + String sql; + // Get insert lock sql string. + sql = H2_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = H2_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = H2_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = H2_LOCK_STORE.getBatchDeleteLockSqlByBranch(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getBatchDeleteLockSqlByBranch(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = H2_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get query lock sql string. + sql = H2_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = H2_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = H2_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + } + + @Test + public void oceanbaseLockTest() { + String sql; + // Get insert lock sql string. + sql = OCEANBASE_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = OCEANBASE_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSqlByBranch(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSqlByBranch(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getBatchDeleteLockSqlByBranchs(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + + // Get query lock sql string. + sql = OCEANBASE_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = OCEANBASE_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, "1"); + Assertions.assertNotNull(sql); + sql = OCEANBASE_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, "1"); + Assertions.assertNotNull(sql); + } +} diff --git a/core/src/test/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java b/core/src/test/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java new file mode 100644 index 0000000..83e3942 --- /dev/null +++ b/core/src/test/java/io/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java @@ -0,0 +1,214 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.core.store.db.sql.log; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author: will + */ +public class LogStoreSqlsFactoryTest { + + private static LogStoreSqls mysqlLog = LogStoreSqlsFactory.getLogStoreSqls("mysql"); + + private static LogStoreSqls oracleLog = LogStoreSqlsFactory.getLogStoreSqls("oracle"); + + private static LogStoreSqls pgLog = LogStoreSqlsFactory.getLogStoreSqls("postgresql"); + + private static LogStoreSqls h2Log = LogStoreSqlsFactory.getLogStoreSqls("h2"); + + private static LogStoreSqls oceanbase = LogStoreSqlsFactory.getLogStoreSqls("oceanbase"); + + private static String globalTable = "global_table"; + + private static String branchTable = "branch_table"; + + @Test + public void mysqlLogTest() { + + String sql = mysqlLog.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = mysqlLog.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } + + @Test + public void oracleLogTest() { + + String sql = oracleLog.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = oracleLog.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } + + @Test + public void pgLogTest() { + + String sql = pgLog.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = pgLog.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = pgLog.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = pgLog.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = pgLog.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } + + @Test + public void h2LogTest() { + + String sql = h2Log.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = h2Log.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = h2Log.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = h2Log.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = h2Log.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } + + @Test + public void oceanbaseLogTest() { + + String sql = oceanbase.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = oceanbase.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } +} diff --git a/discovery/pom.xml b/discovery/pom.xml new file mode 100644 index 0000000..b083a3d --- /dev/null +++ b/discovery/pom.xml @@ -0,0 +1,42 @@ + + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + pom + seata-discovery + seata-discovery ${project.version} + + seata-discovery-consul + seata-discovery-core + seata-discovery-custom + seata-discovery-all + seata-discovery-eureka + seata-discovery-zk + seata-discovery-redis + seata-discovery-nacos + seata-discovery-etcd3 + seata-discovery-sofa + + diff --git a/discovery/seata-discovery-all/pom.xml b/discovery/seata-discovery-all/pom.xml new file mode 100644 index 0000000..af5a3fe --- /dev/null +++ b/discovery/seata-discovery-all/pom.xml @@ -0,0 +1,70 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-all + seata-discovery-all ${project.version} + + + + ${project.groupId} + seata-discovery-consul + ${project.version} + + + ${project.groupId} + seata-discovery-custom + ${project.version} + + + ${project.groupId} + seata-discovery-eureka + ${project.version} + + + ${project.groupId} + seata-discovery-zk + ${project.version} + + + ${project.groupId} + seata-discovery-redis + ${project.version} + + + ${project.groupId} + seata-discovery-nacos + ${project.version} + + + ${project.groupId} + seata-discovery-etcd3 + ${project.version} + + + ${project.groupId} + seata-discovery-sofa + ${project.version} + + + diff --git a/discovery/seata-discovery-consul/pom.xml b/discovery/seata-discovery-consul/pom.xml new file mode 100644 index 0000000..9c01530 --- /dev/null +++ b/discovery/seata-discovery-consul/pom.xml @@ -0,0 +1,47 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-consul + seata-discovery-consul ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + com.ecwid.consul + consul-api + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + + diff --git a/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulListener.java b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulListener.java new file mode 100644 index 0000000..e7efca4 --- /dev/null +++ b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.consul; + +import com.ecwid.consul.v1.health.model.HealthService; + +import java.util.List; + +/** + * @author xingfudeshi@gmail.com + */ +public interface ConsulListener { + /** + * on event + * + * @param services + */ + void onEvent(List services); +} diff --git a/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryProvider.java b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryProvider.java new file mode 100644 index 0000000..6566110 --- /dev/null +++ b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.consul; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Consul", order = 1) +public class ConsulRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return ConsulRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImpl.java b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImpl.java new file mode 100644 index 0000000..afbaa1a --- /dev/null +++ b/discovery/seata-discovery-consul/src/main/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImpl.java @@ -0,0 +1,348 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.consul; + +import com.ecwid.consul.v1.ConsulClient; +import com.ecwid.consul.v1.QueryParams; +import com.ecwid.consul.v1.Response; +import com.ecwid.consul.v1.agent.model.NewService; +import com.ecwid.consul.v1.health.HealthServicesRequest; +import com.ecwid.consul.v1.health.model.HealthService; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.NetUtil; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.config.ConfigurationKeys; +import io.seata.discovery.registry.RegistryService; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author xingfudeshi@gmail.com + */ +public class ConsulRegistryServiceImpl implements RegistryService { + + private static volatile ConsulRegistryServiceImpl instance; + private static volatile ConsulClient client; + + private static final Logger LOGGER = LoggerFactory.getLogger(ConsulRegistryServiceImpl.class); + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String FILE_ROOT_REGISTRY = "registry"; + private static final String FILE_CONFIG_SPLIT_CHAR = "."; + private static final String REGISTRY_TYPE = "consul"; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String REGISTRY_CLUSTER = "cluster"; + private static final String DEFAULT_CLUSTER_NAME = "default"; + private static final String SERVICE_TAG = "services"; + private static final String ACL_TOKEN = "aclToken"; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE + FILE_CONFIG_SPLIT_CHAR; + + private ConcurrentMap> clusterAddressMap; + private ConcurrentMap> listenerMap; + private ExecutorService notifierExecutor; + private ConcurrentMap notifiers; + + private static final int THREAD_POOL_NUM = 1; + private static final int MAP_INITIAL_CAPACITY = 8; + + /** + * default tcp check interval + */ + private static final String DEFAULT_CHECK_INTERVAL = "10s"; + /** + * default tcp check timeout + */ + private static final String DEFAULT_CHECK_TIMEOUT = "1s"; + /** + * default deregister critical server after + */ + private static final String DEFAULT_DEREGISTER_TIME = "20s"; + /** + * default watch timeout in second + */ + private static final int DEFAULT_WATCH_TIMEOUT = 60; + + + private ConsulRegistryServiceImpl() { + //initial the capacity with 8 + clusterAddressMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + listenerMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + notifiers = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + notifierExecutor = new ThreadPoolExecutor(THREAD_POOL_NUM, THREAD_POOL_NUM, + Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), + new NamedThreadFactory("services-consul-notifier", THREAD_POOL_NUM)); + } + + /** + * get instance of ConsulRegistryServiceImpl + * + * @return instance + */ + static ConsulRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (ConsulRegistryServiceImpl.class) { + if (instance == null) { + instance = new ConsulRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + getConsulClient().agentServiceRegister(createService(address), getAclToken()); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + getConsulClient().agentServiceDeregister(createServiceId(address), getAclToken()); + } + + @Override + public void subscribe(String cluster, ConsulListener listener) throws Exception { + //1.add listener to subscribe list + listenerMap.computeIfAbsent(cluster, key -> new HashSet<>()) + .add(listener); + //2.get healthy services + Response> response = getHealthyServices(cluster, -1, DEFAULT_WATCH_TIMEOUT); + //3.get current consul index. + Long index = response.getConsulIndex(); + ConsulNotifier notifier = notifiers.computeIfAbsent(cluster, key -> new ConsulNotifier(cluster, index)); + //4.run notifier + notifierExecutor.submit(notifier); + } + + @Override + public void unsubscribe(String cluster, ConsulListener listener) throws Exception { + //1.remove notifier for the cluster + ConsulNotifier notifier = notifiers.remove(cluster); + //2.stop the notifier + notifier.stop(); + } + + @Override + public List lookup(String key) throws Exception { + final String cluster = getServiceGroup(key); + if (cluster == null) { + return null; + } + if (!listenerMap.containsKey(cluster)) { + //1.refresh cluster + refreshCluster(cluster); + //2. subscribe + subscribe(cluster, services -> refreshCluster(cluster, services)); + } + return clusterAddressMap.get(cluster); + } + + /** + * get consul client + * + * @return client + */ + private ConsulClient getConsulClient() { + if (client == null) { + synchronized (ConsulRegistryServiceImpl.class) { + if (client == null) { + String serverAddr = FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY); + InetSocketAddress inetSocketAddress = NetUtil.toInetSocketAddress(serverAddr); + client = new ConsulClient(inetSocketAddress.getHostName(), inetSocketAddress.getPort()); + } + } + } + return client; + } + + /** + * get cluster name + * + * @return + */ + private String getClusterName() { + String clusterConfigName = String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_CLUSTER); + return FILE_CONFIG.getConfig(clusterConfigName, DEFAULT_CLUSTER_NAME); + } + + /** + * create serviceId + * + * @param address + * @return serviceId + */ + private String createServiceId(InetSocketAddress address) { + return getClusterName() + "-" + NetUtil.toStringAddress(address); + } + + /** + * get consul acl-token + * + * @return acl-token + */ + private static String getAclToken() { + String fileConfigKey = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, ACL_TOKEN); + String aclToken = StringUtils.isNotBlank(System.getProperty(ACL_TOKEN)) ? System.getProperty(ACL_TOKEN) + : FILE_CONFIG.getConfig(fileConfigKey); + return StringUtils.isNotBlank(aclToken) ? aclToken : null; + } + + /** + * create a new service + * + * @param address + * @return newService + */ + private NewService createService(InetSocketAddress address) { + NewService newService = new NewService(); + newService.setId(createServiceId(address)); + newService.setName(getClusterName()); + newService.setTags(Collections.singletonList(SERVICE_TAG)); + newService.setPort(address.getPort()); + newService.setAddress(NetUtil.toIpAddress(address)); + newService.setCheck(createCheck(address)); + return newService; + } + + /** + * create service check based on TCP + * + * @param address + * @return + */ + private NewService.Check createCheck(InetSocketAddress address) { + NewService.Check check = new NewService.Check(); + check.setTcp(NetUtil.toStringAddress(address)); + check.setInterval(DEFAULT_CHECK_INTERVAL); + check.setTimeout(DEFAULT_CHECK_TIMEOUT); + check.setDeregisterCriticalServiceAfter(DEFAULT_DEREGISTER_TIME); + return check; + } + + /** + * get healthy services + * + * @param service + * @return + */ + private Response> getHealthyServices(String service, long index, long watchTimeout) { + return getConsulClient().getHealthServices(service, HealthServicesRequest.newBuilder() + .setTag(SERVICE_TAG) + .setQueryParams(new QueryParams(watchTimeout, index)) + .setPassing(true) + .setToken(getAclToken()) + .build()); + } + + /** + * refresh cluster + * + * @param cluster + */ + private void refreshCluster(String cluster) { + if (cluster == null) { + return; + } + Response> response = getHealthyServices(getClusterName(), -1, -1); + if (response == null) { + return; + } + refreshCluster(cluster, response.getValue()); + } + + /** + * refresh cluster + * + * @param cluster + * @param services + */ + private void refreshCluster(String cluster, List services) { + if (cluster == null || services == null) { + return; + } + clusterAddressMap.put(cluster, services.stream() + .map(HealthService::getService) + .map(service -> new InetSocketAddress(service.getAddress(), service.getPort())) + .collect(Collectors.toList())); + } + + /** + * consul notifier + */ + private class ConsulNotifier implements Runnable { + private String cluster; + private long consulIndex; + private boolean running; + private boolean hasError = false; + + ConsulNotifier(String cluster, long consulIndex) { + this.cluster = cluster; + this.consulIndex = consulIndex; + this.running = true; + } + + @Override + public void run() { + while (this.running) { + try { + processService(); + } catch (Exception exception) { + hasError = true; + LOGGER.error("consul refresh services error:{}", exception.getMessage()); + } + } + } + + private void processService() { + Response> response = getHealthyServices(cluster, consulIndex, DEFAULT_WATCH_TIMEOUT); + Long currentIndex = response.getConsulIndex(); + + if ((currentIndex != null && currentIndex > consulIndex) || hasError) { + hasError = false; + List services = response.getValue(); + consulIndex = currentIndex; + for (ConsulListener listener : listenerMap.get(cluster)) { + listener.onEvent(services); + } + } + } + + void stop() { + this.running = false; + } + } + + @Override + public void close() throws Exception { + client = null; + } + +} diff --git a/discovery/seata-discovery-consul/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-consul/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..40fb3dd --- /dev/null +++ b/discovery/seata-discovery-consul/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.consul.ConsulRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-consul/src/test/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImplTest.java b/discovery/seata-discovery-consul/src/test/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImplTest.java new file mode 100644 index 0000000..99f2502 --- /dev/null +++ b/discovery/seata-discovery-consul/src/test/java/io/seata/discovery/registry/consul/ConsulRegistryServiceImplTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.consul; + +import io.seata.discovery.registry.RegistryService; +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + + +/** + * @author xingfudeshi@gmail.com + */ +public class ConsulRegistryServiceImplTest { + + + @Test + public void testRegister() throws Exception { + RegistryService registryService = mock(ConsulRegistryServiceImpl.class); + InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8091); + registryService.register(inetSocketAddress); + verify(registryService).register(inetSocketAddress); + } + + @Test + public void testUnregister() throws Exception { + RegistryService registryService = mock(ConsulRegistryServiceImpl.class); + InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8091); + registryService.unregister(inetSocketAddress); + verify(registryService).unregister(inetSocketAddress); + } + + @Test + public void testSubscribe() throws Exception { + RegistryService registryService = mock(ConsulRegistryServiceImpl.class); + ConsulListener consulListener = mock(ConsulListener.class); + registryService.subscribe("test", consulListener); + verify(registryService).subscribe("test", consulListener); + } + + @Test + public void testLookup() throws Exception { + RegistryService registryService = mock(ConsulRegistryServiceImpl.class); + registryService.lookup("test-key"); + verify(registryService).lookup("test-key"); + } +} diff --git a/discovery/seata-discovery-core/pom.xml b/discovery/seata-discovery-core/pom.xml new file mode 100644 index 0000000..bafd011 --- /dev/null +++ b/discovery/seata-discovery-core/pom.xml @@ -0,0 +1,35 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-core + seata-discovery-core ${project.version} + + + + ${project.groupId} + seata-config-core + ${project.version} + + + diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/AbstractLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/AbstractLoadBalance.java new file mode 100644 index 0000000..bb2fbfe --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/AbstractLoadBalance.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.util.List; + +import io.seata.common.util.CollectionUtils; + +/** + * The type Abstract load balance. + * + * @author slievrly + */ +public abstract class AbstractLoadBalance implements LoadBalance { + + @Override + public T select(List invokers, String xid) { + if (CollectionUtils.isEmpty(invokers)) { + return null; + } + if (invokers.size() == 1) { + return invokers.get(0); + } + return doSelect(invokers, xid); + } + + /** + * Do select t. + * + * @param the type parameter + * @param invokers the invokers + * @param xid the xid + * @return the t + */ + protected abstract T doSelect(List invokers, String xid); +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/ConsistentHashLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/ConsistentHashLoadBalance.java new file mode 100644 index 0000000..28b2461 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/ConsistentHashLoadBalance.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import io.seata.common.loader.LoadLevel; +import io.seata.config.ConfigurationFactory; + +import static io.seata.common.DefaultValues.VIRTUAL_NODES_DEFAULT; +import static io.seata.discovery.loadbalance.LoadBalanceFactory.CONSISTENT_HASH_LOAD_BALANCE; +import static io.seata.discovery.loadbalance.LoadBalanceFactory.LOAD_BALANCE_PREFIX; +/** + * The type consistent hash load balance. + * + * @author ph3636 + */ +@LoadLevel(name = CONSISTENT_HASH_LOAD_BALANCE) +public class ConsistentHashLoadBalance extends AbstractLoadBalance { + + /** + * The constant LOAD_BALANCE_CONSISTENT_HASH_VISUAL_NODES. + */ + public static final String LOAD_BALANCE_CONSISTENT_HASH_VISUAL_NODES = LOAD_BALANCE_PREFIX + "visualNodes"; + /** + * The constant VIRTUAL_NODES_NUM. + */ + private static final int VIRTUAL_NODES_NUM = ConfigurationFactory.getInstance().getInt(LOAD_BALANCE_CONSISTENT_HASH_VISUAL_NODES, VIRTUAL_NODES_DEFAULT); + + @Override + protected T doSelect(List invokers, String xid) { + return new ConsistentHashSelector<>(invokers, VIRTUAL_NODES_NUM).select(xid); + } + + private static final class ConsistentHashSelector { + + private final SortedMap virtualInvokers = new TreeMap<>(); + private final HashFunction hashFunction = new MD5Hash(); + + ConsistentHashSelector(List invokers, int virtualNodes) { + for (T invoker : invokers) { + for (int i = 0; i < virtualNodes; i++) { + virtualInvokers.put(hashFunction.hash(invoker.toString() + i), invoker); + } + } + } + + public T select(String objectKey) { + SortedMap tailMap = virtualInvokers.tailMap(hashFunction.hash(objectKey)); + Long nodeHashVal = tailMap.isEmpty() ? virtualInvokers.firstKey() : tailMap.firstKey(); + return virtualInvokers.get(nodeHashVal); + } + } + + private static class MD5Hash implements HashFunction { + MessageDigest instance; + public MD5Hash() { + try { + instance = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + @Override + public long hash(String key) { + instance.reset(); + instance.update(key.getBytes()); + byte[] digest = instance.digest(); + long h = 0; + for (int i = 0; i < 4; i++) { + h <<= 8; + h |= ((int) digest[i]) & 0xFF; + } + return h; + } + } + + /** + * Hash String to long value + */ + public interface HashFunction { + long hash(String key); + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LeastActiveLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LeastActiveLoadBalance.java new file mode 100644 index 0000000..f1ed208 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LeastActiveLoadBalance.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import io.seata.common.loader.LoadLevel; +import io.seata.common.rpc.RpcStatus; + +import static io.seata.discovery.loadbalance.LoadBalanceFactory.LEAST_ACTIVE_LOAD_BALANCE; + +/** + * The type Least Active load balance. + * + * @author ph3636 + */ +@LoadLevel(name = LEAST_ACTIVE_LOAD_BALANCE) +public class LeastActiveLoadBalance extends AbstractLoadBalance { + + @Override + protected T doSelect(List invokers, String xid) { + int length = invokers.size(); + long leastActive = -1; + int leastCount = 0; + int[] leastIndexes = new int[length]; + for (int i = 0; i < length; i++) { + long active = RpcStatus.getStatus(invokers.get(i).toString()).getActive(); + if (leastActive == -1 || active < leastActive) { + leastActive = active; + leastCount = 1; + leastIndexes[0] = i; + } else if (active == leastActive) { + leastIndexes[leastCount++] = i; + } + } + if (leastCount == 1) { + return invokers.get(leastIndexes[0]); + } + return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]); + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java new file mode 100644 index 0000000..be8b64a --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalance.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.util.List; + +/** + * The interface Load balance. + * + * @author slievrly + */ +public interface LoadBalance { + + /** + * Select t. + * + * @param the type parameter + * @param invokers the invokers + * @param xid the xid + * @return the t + * @throws Exception the exception + */ + T select(List invokers, String xid) throws Exception; +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalanceFactory.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalanceFactory.java new file mode 100644 index 0000000..d9489c3 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/LoadBalanceFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.config.ConfigurationFactory; + +import static io.seata.common.DefaultValues.DEFAULT_LOAD_BALANCE; + +/** + * The type Load balance factory. + * + * @author slievrly + */ +public class LoadBalanceFactory { + + private static final String CLIENT_PREFIX = "client."; + /** + * The constant LOAD_BALANCE_PREFIX. + */ + public static final String LOAD_BALANCE_PREFIX = CLIENT_PREFIX + "loadBalance."; + + public static final String LOAD_BALANCE_TYPE = LOAD_BALANCE_PREFIX + "type"; + + public static final String RANDOM_LOAD_BALANCE = DEFAULT_LOAD_BALANCE; + + public static final String ROUND_ROBIN_LOAD_BALANCE = "RoundRobinLoadBalance"; + + public static final String CONSISTENT_HASH_LOAD_BALANCE = "ConsistentHashLoadBalance"; + + public static final String LEAST_ACTIVE_LOAD_BALANCE = "LeastActiveLoadBalance"; + + + /** + * Get instance. + * + * @return the instance + */ + public static LoadBalance getInstance() { + String config = ConfigurationFactory.getInstance().getConfig(LOAD_BALANCE_TYPE, DEFAULT_LOAD_BALANCE); + return EnhancedServiceLoader.load(LoadBalance.class, config); + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RandomLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RandomLoadBalance.java new file mode 100644 index 0000000..732385b --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RandomLoadBalance.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import io.seata.common.loader.LoadLevel; + +import static io.seata.discovery.loadbalance.LoadBalanceFactory.RANDOM_LOAD_BALANCE; + +/** + * The type Random load balance. + * + * @author yuoyao + */ +@LoadLevel(name = RANDOM_LOAD_BALANCE) +public class RandomLoadBalance extends AbstractLoadBalance { + + @Override + protected T doSelect(List invokers, String xid) { + int length = invokers.size(); + return invokers.get(ThreadLocalRandom.current().nextInt(length)); + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RoundRobinLoadBalance.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RoundRobinLoadBalance.java new file mode 100644 index 0000000..c0ba32e --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/loadbalance/RoundRobinLoadBalance.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import io.seata.common.loader.LoadLevel; + +import static io.seata.discovery.loadbalance.LoadBalanceFactory.ROUND_ROBIN_LOAD_BALANCE; + +/** + * The type Round robin load balance. + * + * @author slievrly + */ +@LoadLevel(name = ROUND_ROBIN_LOAD_BALANCE) +public class RoundRobinLoadBalance extends AbstractLoadBalance { + + private final AtomicInteger sequence = new AtomicInteger(); + + @Override + protected T doSelect(List invokers, String xid) { + int length = invokers.size(); + return invokers.get(getPositiveSequence() % length); + } + + private int getPositiveSequence() { + for (; ; ) { + int current = sequence.get(); + int next = current >= Integer.MAX_VALUE ? 0 : current + 1; + if (sequence.compareAndSet(current, next)) { + return current; + } + } + } + +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java new file mode 100644 index 0000000..6bf4da7 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/FileRegistryServiceImpl.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry; + + +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigChangeListener; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +/** + * The type File registry service. + * + * @author slievrly + */ +public class FileRegistryServiceImpl implements RegistryService { + private static volatile FileRegistryServiceImpl instance; + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + private static final String POSTFIX_GROUPLIST = ".grouplist"; + private static final String ENDPOINT_SPLIT_CHAR = ";"; + private static final String IP_PORT_SPLIT_CHAR = ":"; + + private FileRegistryServiceImpl() { + } + + /** + * Gets instance. + * + * @return the instance + */ + static FileRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (FileRegistryServiceImpl.class) { + if (instance == null) { + instance = new FileRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + + } + + @Override + public void subscribe(String cluster, ConfigChangeListener listener) throws Exception { + + } + + @Override + public void unsubscribe(String cluster, ConfigChangeListener listener) throws Exception { + + } + + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + String endpointStr = CONFIG.getConfig( + PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + clusterName + POSTFIX_GROUPLIST); + if (StringUtils.isNullOrEmpty(endpointStr)) { + throw new IllegalArgumentException(clusterName + POSTFIX_GROUPLIST + " is required"); + } + String[] endpoints = endpointStr.split(ENDPOINT_SPLIT_CHAR); + List inetSocketAddresses = new ArrayList<>(); + for (String endpoint : endpoints) { + String[] ipAndPort = endpoint.split(IP_PORT_SPLIT_CHAR); + if (ipAndPort.length != 2) { + throw new IllegalArgumentException("endpoint format should like ip:port"); + } + inetSocketAddresses.add(new InetSocketAddress(ipAndPort[0], Integer.parseInt(ipAndPort[1]))); + } + return inetSocketAddresses; + } + + @Override + public void close() throws Exception { + + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryFactory.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryFactory.java new file mode 100644 index 0000000..99e7b89 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry; + +import java.util.Objects; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.config.ConfigurationFactory; +import io.seata.config.ConfigurationKeys; + +/** + * The type Registry factory. + * + * @author slievrly + */ +public class RegistryFactory { + + private static volatile RegistryService instance = null; + + /** + * Gets instance. + * + * @return the instance + */ + public static RegistryService getInstance() { + if (instance == null) { + synchronized (RegistryFactory.class) { + if (instance == null) { + instance = buildRegistryService(); + } + } + } + return instance; + } + + private static RegistryService buildRegistryService() { + RegistryType registryType; + String registryTypeName = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig( + ConfigurationKeys.FILE_ROOT_REGISTRY + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + + ConfigurationKeys.FILE_ROOT_TYPE); + try { + registryType = RegistryType.getType(registryTypeName); + } catch (Exception exx) { + throw new NotSupportYetException("not support registry type: " + registryTypeName); + } + if (RegistryType.File == registryType) { + return FileRegistryServiceImpl.getInstance(); + } else { + return EnhancedServiceLoader.load(RegistryProvider.class, Objects.requireNonNull(registryType).name()).provide(); + } + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryProvider.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryProvider.java new file mode 100644 index 0000000..402eb48 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry; + +/** + * the interface registry provider + * @author xingfudeshi@gmail.com + */ +public interface RegistryProvider { + /** + * provide a registry implementation instance + * @return RegistryService + */ + RegistryService provide(); +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryService.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryService.java new file mode 100644 index 0000000..2e4fc3d --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryService.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry; + +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import io.seata.config.ConfigurationCache; +import io.seata.config.ConfigurationFactory; + +/** + * The interface Registry service. + * + * @param the type parameter + * @author slievrly + */ +public interface RegistryService { + + /** + * The constant PREFIX_SERVICE_MAPPING. + */ + String PREFIX_SERVICE_MAPPING = "vgroupMapping."; + /** + * The constant PREFIX_SERVICE_ROOT. + */ + String PREFIX_SERVICE_ROOT = "service"; + /** + * The constant CONFIG_SPLIT_CHAR. + */ + String CONFIG_SPLIT_CHAR = "."; + + Set SERVICE_GROUP_NAME = new HashSet<>(); + + /** + * Register. + * + * @param address the address + * @throws Exception the exception + */ + void register(InetSocketAddress address) throws Exception; + + /** + * Unregister. + * + * @param address the address + * @throws Exception the exception + */ + void unregister(InetSocketAddress address) throws Exception; + + /** + * Subscribe. + * + * @param cluster the cluster + * @param listener the listener + * @throws Exception the exception + */ + void subscribe(String cluster, T listener) throws Exception; + + /** + * Unsubscribe. + * + * @param cluster the cluster + * @param listener the listener + * @throws Exception the exception + */ + void unsubscribe(String cluster, T listener) throws Exception; + + /** + * Lookup list. + * + * @param key the key + * @return the list + * @throws Exception the exception + */ + List lookup(String key) throws Exception; + + /** + * Close. + * @throws Exception + */ + void close() throws Exception; + + /** + * Get current service group name + * + * @param key service group + * @return the service group name + */ + default String getServiceGroup(String key) { + key = PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + PREFIX_SERVICE_MAPPING + key; + if (!SERVICE_GROUP_NAME.contains(key)) { + ConfigurationCache.addConfigListener(key); + SERVICE_GROUP_NAME.add(key); + } + return ConfigurationFactory.getInstance().getConfig(key); + } +} diff --git a/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryType.java b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryType.java new file mode 100644 index 0000000..78636f8 --- /dev/null +++ b/discovery/seata-discovery-core/src/main/java/io/seata/discovery/registry/RegistryType.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry; + +/** + * The enum Registry type. + * + * @author slievrly + */ +public enum RegistryType { + /** + * File registry type. + */ + File, + /** + * ZK registry type. + */ + ZK, + /** + * Redis registry type. + */ + Redis, + /** + * Nacos registry type. + */ + Nacos, + /** + * Eureka registry type. + */ + Eureka, + /** + * Consul registry type + */ + Consul, + /** + * Etcd3 registry type + */ + Etcd3, + /** + * Sofa registry type + */ + Sofa, + /** + * Custom registry type + */ + Custom; + + /** + * Gets type. + * + * @param name the name + * @return the type + */ + public static RegistryType getType(String name) { + for (RegistryType registryType : RegistryType.values()) { + if (registryType.name().equalsIgnoreCase(name)) { + return registryType; + } + } + throw new IllegalArgumentException("not support registry type: " + name); + } +} diff --git a/discovery/seata-discovery-core/src/main/resources/META-INF/services/io.seata.discovery.loadbalance.LoadBalance b/discovery/seata-discovery-core/src/main/resources/META-INF/services/io.seata.discovery.loadbalance.LoadBalance new file mode 100644 index 0000000..26a29ae --- /dev/null +++ b/discovery/seata-discovery-core/src/main/resources/META-INF/services/io.seata.discovery.loadbalance.LoadBalance @@ -0,0 +1,52 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.discovery.loadbalance.RoundRobinLoadBalance +io.seata.discovery.loadbalance.RandomLoadBalance +io.seata.discovery.loadbalance.ConsistentHashLoadBalance +io.seata.discovery.loadbalance.LeastActiveLoadBalance \ No newline at end of file diff --git a/discovery/seata-discovery-core/src/test/java/io/seata/config/ConfigurationFactoryTest.java b/discovery/seata-discovery-core/src/test/java/io/seata/config/ConfigurationFactoryTest.java new file mode 100644 index 0000000..2e26179 --- /dev/null +++ b/discovery/seata-discovery-core/src/test/java/io/seata/config/ConfigurationFactoryTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.config; + +import io.seata.discovery.loadbalance.ConsistentHashLoadBalance; +import io.seata.discovery.loadbalance.LoadBalanceFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +class ConfigurationFactoryTest { + + @Test + void getInstance() { + Configuration configuration = ConfigurationFactory.getInstance(); + // check singleton + Assertions.assertEquals(configuration.getClass().getName(), ConfigurationFactory.getInstance().getClass().getName()); + } + + + @Test + void getLoadBalance() { + Configuration configuration = ConfigurationFactory.getInstance(); + String loadBalanceType = configuration.getConfig(LoadBalanceFactory.LOAD_BALANCE_TYPE); + int visualNode = configuration.getInt(ConsistentHashLoadBalance.LOAD_BALANCE_CONSISTENT_HASH_VISUAL_NODES); + Assertions.assertEquals("RandomLoadBalance", loadBalanceType); + Assertions.assertEquals(10,visualNode); + } + +} \ No newline at end of file diff --git a/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceFactoryTest.java b/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceFactoryTest.java new file mode 100644 index 0000000..c846bc7 --- /dev/null +++ b/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceFactoryTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import io.seata.discovery.registry.RegistryFactory; +import io.seata.discovery.registry.RegistryService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +/** + * The type Load balance factory test. + * + * @author slievrly + */ +public class LoadBalanceFactoryTest { + + private static final String XID = "XID"; + + /** + * Test get registry. + * + * @param loadBalance the load balance + * @throws Exception the exception + */ + @ParameterizedTest + @MethodSource("instanceProvider") + @Disabled + public void testGetRegistry(LoadBalance loadBalance) throws Exception { + Assertions.assertNotNull(loadBalance); + RegistryService registryService = RegistryFactory.getInstance(); + InetSocketAddress address1 = new InetSocketAddress("127.0.0.1", 8091); + InetSocketAddress address2 = new InetSocketAddress("127.0.0.1", 8092); + registryService.register(address1); + registryService.register(address2); + List addressList = registryService.lookup("my_test_tx_group"); + InetSocketAddress balanceAddress = loadBalance.select(addressList, XID); + Assertions.assertNotNull(balanceAddress); + } + + /** + * Test get address. + * + * @param loadBalance the load balance + * @throws Exception the exception + */ + @ParameterizedTest + @MethodSource("instanceProvider") + @Disabled + public void testUnRegistry(LoadBalance loadBalance) throws Exception { + RegistryService registryService = RegistryFactory.getInstance(); + InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8091); + registryService.unregister(address); + } + + /** + * Test subscribe. + * + * @param loadBalance the load balance + * @throws Exception the exception + */ + @ParameterizedTest + @MethodSource("instanceProvider") + @Disabled + public void testSubscribe(LoadBalance loadBalance) throws Exception { + Assertions.assertNotNull(loadBalance); + RegistryService registryService = RegistryFactory.getInstance(); + InetSocketAddress address1 = new InetSocketAddress("127.0.0.1", 8091); + InetSocketAddress address2 = new InetSocketAddress("127.0.0.1", 8092); + registryService.register(address1); + registryService.register(address2); + List addressList = registryService.lookup("my_test_tx_group"); + InetSocketAddress balanceAddress = loadBalance.select(addressList, XID); + Assertions.assertNotNull(balanceAddress); + //wait trigger testUnRegistry + TimeUnit.SECONDS.sleep(30); + List addressList1 = registryService.lookup("my_test_tx_group"); + Assertions.assertEquals(1, addressList1.size()); + } + + /** + * Test get address. + * + * @param loadBalance the load balance + * @throws Exception the exception + */ + @ParameterizedTest + @MethodSource("instanceProvider") + public void testGetAddress(LoadBalance loadBalance) throws Exception { + Assertions.assertNotNull(loadBalance); + InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8091); + List addressList = new ArrayList<>(); + addressList.add(address); + InetSocketAddress balanceAddress = loadBalance.select(addressList, XID); + Assertions.assertEquals(address, balanceAddress); + } + + /** + * Instance provider object [ ] [ ]. + * + * @return the object [ ] [ ] + */ + static Stream instanceProvider() { + LoadBalance loadBalance = LoadBalanceFactory.getInstance(); + return Stream.of( + Arguments.of(loadBalance) + ); + } +} diff --git a/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceTest.java b/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceTest.java new file mode 100644 index 0000000..41a88e3 --- /dev/null +++ b/discovery/seata-discovery-core/src/test/java/io/seata/discovery/loadbalance/LoadBalanceTest.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.loadbalance; + +import io.seata.common.rpc.RpcStatus; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +/** + * Created by guoyao on 2019/2/14. + */ +public class LoadBalanceTest { + + private static final String XID = "XID"; + + /** + * Test random load balance select. + * + * @param addresses the addresses + */ + @ParameterizedTest + @MethodSource("addressProvider") + public void testRandomLoadBalance_select(List addresses) { + int runs = 10000; + Map counter = getSelectedCounter(runs, addresses, new RandomLoadBalance()); + for (InetSocketAddress address : counter.keySet()) { + Long count = counter.get(address).get(); + Assertions.assertTrue(count > 0, "selecte one time at last"); + } + } + + /** + * Test round robin load balance select. + * + * @param addresses the addresses + */ + @ParameterizedTest + @MethodSource("addressProvider") + public void testRoundRobinLoadBalance_select(List addresses) { + int runs = 10000; + Map counter = getSelectedCounter(runs, addresses, new RoundRobinLoadBalance()); + for (InetSocketAddress address : counter.keySet()) { + Long count = counter.get(address).get(); + Assertions.assertTrue(Math.abs(count - runs / (0f + addresses.size())) < 1f, "abs diff shoud < 1"); + } + } + + /** + * Test consistent hash load load balance select. + * + * @param addresses the addresses + */ + @ParameterizedTest + @MethodSource("addressProvider") + public void testConsistentHashLoadBalance_select(List addresses) { + int runs = 10000; + int selected = 0; + ConsistentHashLoadBalance loadBalance = new ConsistentHashLoadBalance(); + Map counter = getSelectedCounter(runs, addresses, loadBalance); + for (InetSocketAddress address : counter.keySet()) { + if (counter.get(address).get() > 0) { + selected++; + } + } + Assertions.assertEquals(1, selected, "selected must be equal to 1"); + } + + /** + * Test least active load balance select. + * + * @param addresses the addresses + */ + @ParameterizedTest + @MethodSource("addressProvider") + public void testLeastActiveLoadBalance_select(List addresses) throws Exception { + int runs = 10000; + int size = addresses.size(); + for (int i = 0; i < size - 1; i++) { + RpcStatus.beginCount(addresses.get(i).toString()); + } + InetSocketAddress socketAddress = addresses.get(size - 1); + LoadBalance loadBalance = new LeastActiveLoadBalance(); + for (int i = 0; i < runs; i++) { + InetSocketAddress selectAddress = loadBalance.select(addresses, XID); + Assertions.assertEquals(selectAddress, socketAddress); + } + RpcStatus.beginCount(socketAddress.toString()); + RpcStatus.beginCount(socketAddress.toString()); + Map counter = getSelectedCounter(runs, addresses, loadBalance); + for (InetSocketAddress address : counter.keySet()) { + Long count = counter.get(address).get(); + if (address == socketAddress) { + Assertions.assertEquals(count, 0); + } else { + Assertions.assertTrue(count > 0); + } + } + } + + /** + * Gets selected counter. + * + * @param runs the runs + * @param addresses the addresses + * @param loadBalance the load balance + * @return the selected counter + */ + public Map getSelectedCounter(int runs, List addresses, + LoadBalance loadBalance) { + Assertions.assertNotNull(loadBalance); + Map counter = new ConcurrentHashMap<>(); + for (InetSocketAddress address : addresses) { + counter.put(address, new AtomicLong(0)); + } + try { + for (int i = 0; i < runs; i++) { + InetSocketAddress selectAddress = loadBalance.select(addresses, XID); + counter.get(selectAddress).incrementAndGet(); + } + } catch (Exception e) { + //do nothing + } + return counter; + } + + /** + * Address provider object [ ] [ ]. + * + * @return Stream> + */ + static Stream> addressProvider() { + return Stream.of( + Arrays.asList(new InetSocketAddress("127.0.0.1", 8091), + new InetSocketAddress("127.0.0.1", 8092), + new InetSocketAddress("127.0.0.1", 8093), + new InetSocketAddress("127.0.0.1", 8094), + new InetSocketAddress("127.0.0.1", 8095)) + ); + } +} diff --git a/discovery/seata-discovery-core/src/test/resources/apollo.properties b/discovery/seata-discovery-core/src/test/resources/apollo.properties new file mode 100644 index 0000000..d2575b5 --- /dev/null +++ b/discovery/seata-discovery-core/src/test/resources/apollo.properties @@ -0,0 +1,21 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#apollo config +registry.redis.serverAddr = 192.168.1.204:6379 +registry.redis.db = 6 +registry.redis.max.active = 16 +service.vgroupMapping.my_test_tx_group = default diff --git a/discovery/seata-discovery-core/src/test/resources/file.conf b/discovery/seata-discovery-core/src/test/resources/file.conf new file mode 100644 index 0000000..12e1340 --- /dev/null +++ b/discovery/seata-discovery-core/src/test/resources/file.conf @@ -0,0 +1,23 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#loadBalance config +client { + loadBalance { + type = "RandomLoadBalance" + visualNodes = 10 + } +} \ No newline at end of file diff --git a/discovery/seata-discovery-custom/pom.xml b/discovery/seata-discovery-custom/pom.xml new file mode 100644 index 0000000..f2315be --- /dev/null +++ b/discovery/seata-discovery-custom/pom.xml @@ -0,0 +1,35 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-custom + seata-discovery-custom ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + diff --git a/discovery/seata-discovery-custom/src/main/java/io/seata/discovery/registry/custom/CustomRegistryProvider.java b/discovery/seata-discovery-custom/src/main/java/io/seata/discovery/registry/custom/CustomRegistryProvider.java new file mode 100644 index 0000000..8b740b7 --- /dev/null +++ b/discovery/seata-discovery-custom/src/main/java/io/seata/discovery/registry/custom/CustomRegistryProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.custom; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; +import io.seata.discovery.registry.RegistryType; + +import java.util.stream.Stream; + +/** + * @author ggndnn + */ +@LoadLevel(name = "Custom") +public class CustomRegistryProvider implements RegistryProvider { + private static final String FILE_CONFIG_KEY_PREFIX = "registry.custom.name"; + + private final String customName; + + public CustomRegistryProvider() { + String name = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(FILE_CONFIG_KEY_PREFIX); + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("name value of custom registry type must not be blank"); + } + if (Stream.of(RegistryType.values()) + .anyMatch(ct -> ct.name().equalsIgnoreCase(name))) { + throw new IllegalArgumentException(String.format("custom registry type name %s is not allowed", name)); + } + customName = name; + } + + @Override + public RegistryService provide() { + return EnhancedServiceLoader.load(RegistryProvider.class, customName).provide(); + } +} diff --git a/discovery/seata-discovery-custom/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-custom/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..8ad4d65 --- /dev/null +++ b/discovery/seata-discovery-custom/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.custom.CustomRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryProviderForTest.java b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryProviderForTest.java new file mode 100644 index 0000000..b13b3ac --- /dev/null +++ b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryProviderForTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.custom; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +@LoadLevel(name = "forTest") +public class CustomRegistryProviderForTest implements RegistryProvider { + @Override + public RegistryService provide() { + return new CustomRegistryServiceForTest(); + } +} diff --git a/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryServiceForTest.java b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryServiceForTest.java new file mode 100644 index 0000000..804ef1f --- /dev/null +++ b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryServiceForTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.custom; + +import io.seata.config.ConfigChangeListener; +import io.seata.discovery.registry.RegistryService; + +import java.net.InetSocketAddress; +import java.util.List; + +public class CustomRegistryServiceForTest implements RegistryService { + @Override + public void register(InetSocketAddress address) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public void subscribe(String cluster, ConfigChangeListener listener) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public void unsubscribe(String cluster, ConfigChangeListener listener) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public List lookup(String key) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public void close() throws Exception { + throw new UnsupportedOperationException(); + } + +} diff --git a/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryTest.java b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryTest.java new file mode 100644 index 0000000..0aa7d42 --- /dev/null +++ b/discovery/seata-discovery-custom/src/test/java/io/seata/discovery/registry/custom/CustomRegistryTest.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.custom; + +import io.seata.discovery.registry.RegistryFactory; +import io.seata.discovery.registry.RegistryService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CustomRegistryTest { + @Test + public void testCustomRegistryLoad() { + RegistryService registryService = RegistryFactory.getInstance(); + Assertions.assertTrue(registryService instanceof CustomRegistryServiceForTest); + } +} diff --git a/discovery/seata-discovery-custom/src/test/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-custom/src/test/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..83cd144 --- /dev/null +++ b/discovery/seata-discovery-custom/src/test/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.custom.CustomRegistryProviderForTest \ No newline at end of file diff --git a/discovery/seata-discovery-custom/src/test/resources/registry.conf b/discovery/seata-discovery-custom/src/test/resources/registry.conf new file mode 100644 index 0000000..beef5ee --- /dev/null +++ b/discovery/seata-discovery-custom/src/test/resources/registry.conf @@ -0,0 +1,7 @@ +registry { + type = "custom" + + custom { + name = "forTest" + } +} \ No newline at end of file diff --git a/discovery/seata-discovery-etcd3/pom.xml b/discovery/seata-discovery-etcd3/pom.xml new file mode 100644 index 0000000..892290a --- /dev/null +++ b/discovery/seata-discovery-etcd3/pom.xml @@ -0,0 +1,60 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-etcd3 + seata-discovery-etcd3 ${project.version} + + + + ${project.groupId} + seata-discovery-core + ${project.parent.version} + + + io.etcd + jetcd-core + + + com.google.guava + guava + + + + + com.google.guava + guava + + + + io.etcd + jetcd-launcher + test + + + org.testcontainers + testcontainers + test + + + diff --git a/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryProvider.java b/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryProvider.java new file mode 100644 index 0000000..5cd8094 --- /dev/null +++ b/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.etcd3; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Etcd3", order = 1) +public class EtcdRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return EtcdRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryServiceImpl.java b/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryServiceImpl.java new file mode 100644 index 0000000..748eb6d --- /dev/null +++ b/discovery/seata-discovery-etcd3/src/main/java/io/seata/discovery/registry/etcd3/EtcdRegistryServiceImpl.java @@ -0,0 +1,439 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.etcd3; + + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.Lease; +import io.etcd.jetcd.Watch; +import io.etcd.jetcd.kv.GetResponse; +import io.etcd.jetcd.lease.LeaseTimeToLiveResponse; +import io.etcd.jetcd.options.GetOption; +import io.etcd.jetcd.options.LeaseOption; +import io.etcd.jetcd.options.PutOption; +import io.etcd.jetcd.options.WatchOption; +import io.etcd.jetcd.watch.WatchResponse; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static io.netty.util.CharsetUtil.UTF_8; + +/** + * @author xingfudeshi@gmail.com + */ +public class EtcdRegistryServiceImpl implements RegistryService { + private static final Logger LOGGER = LoggerFactory.getLogger(EtcdRegistryServiceImpl.class); + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String FILE_ROOT_REGISTRY = "registry"; + private static final String FILE_CONFIG_SPLIT_CHAR = "."; + private static final String REGISTRY_TYPE = "etcd3"; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String REGISTRY_CLUSTER = "cluster"; + private static final String DEFAULT_CLUSTER_NAME = "default"; + private static final String REGISTRY_KEY_PREFIX = "registry-seata-"; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE + FILE_CONFIG_SPLIT_CHAR; + private static final int MAP_INITIAL_CAPACITY = 8; + private static final int THREAD_POOL_SIZE = 2; + private ExecutorService executorService; + /** + * TTL for lease + */ + private static final long TTL = 10; + /** + * interval for life keep + */ + private final static long LIFE_KEEP_INTERVAL = 5; + /** + * critical value for life keep + */ + private final static long LIFE_KEEP_CRITICAL = 6; + private static volatile EtcdRegistryServiceImpl instance; + private static volatile Client client; + private ConcurrentMap>> clusterAddressMap; + private ConcurrentMap> listenerMap; + private ConcurrentMap watcherMap; + private static long leaseId = 0; + private EtcdLifeKeeper lifeKeeper = null; + private Future lifeKeeperFuture = null; + /** + * a endpoint for unit testing + */ + public static final String TEST_ENDPONT = "etcd-test-lancher-endpoint"; + + + private EtcdRegistryServiceImpl() { + clusterAddressMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + listenerMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + watcherMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("registry-etcd3", THREAD_POOL_SIZE)); + } + + /** + * get etcd registry service instance + * + * @return instance + */ + static EtcdRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (EtcdRegistryServiceImpl.class) { + if (instance == null) { + instance = new EtcdRegistryServiceImpl(); + } + } + } + return instance; + } + + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + doRegister(address); + } + + /** + * do registry + * + * @param address + */ + private void doRegister(InetSocketAddress address) throws Exception { + PutOption putOption = PutOption.newBuilder().withLeaseId(getLeaseId()).build(); + getClient().getKVClient().put(buildRegistryKey(address), buildRegistryValue(address), putOption).get(); + } + + + @Override + public void unregister(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + doUnregister(address); + } + + /** + * do unregister + * + * @param address + * @throws Exception + */ + private void doUnregister(InetSocketAddress address) throws Exception { + getClient().getKVClient().delete(buildRegistryKey(address)).get(); + } + + @Override + public void subscribe(String cluster, Watch.Listener listener) throws Exception { + listenerMap.computeIfAbsent(cluster, key -> new HashSet<>()) + .add(listener); + EtcdWatcher watcher = watcherMap.computeIfAbsent(cluster, w -> new EtcdWatcher(cluster, listener)); + executorService.submit(watcher); + } + + @Override + public void unsubscribe(String cluster, Watch.Listener listener) throws Exception { + Set subscribeSet = listenerMap.get(cluster); + if (subscribeSet != null) { + Set newSubscribeSet = subscribeSet.stream() + .filter(eventListener -> !eventListener.equals(listener)) + .collect(Collectors.toSet()); + listenerMap.put(cluster, newSubscribeSet); + } + watcherMap.remove(cluster).stop(); + } + + @Override + public List lookup(String key) throws Exception { + final String cluster = getServiceGroup(key); + if (cluster == null) { + return null; + } + if (!listenerMap.containsKey(cluster)) { + //1.refresh + refreshCluster(cluster); + //2.subscribe + subscribe(cluster, new Watch.Listener() { + @Override + public void onNext(WatchResponse response) { + try { + refreshCluster(cluster); + } catch (Exception e) { + LOGGER.error("etcd watch listener", e); + throw new RuntimeException(e.getMessage()); + } + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onCompleted() { + + } + }); + + } + Pair> pair = clusterAddressMap.get(cluster); + return Objects.isNull(pair) ? Collections.emptyList() : pair.getValue(); + } + + @Override + public void close() throws Exception { + if (lifeKeeper != null) { + lifeKeeper.stop(); + if (lifeKeeperFuture != null) { + lifeKeeperFuture.get(3, TimeUnit.SECONDS); + } + } + + } + + /** + * refresh cluster + * + * @param cluster + * @throws Exception + */ + private void refreshCluster(String cluster) throws Exception { + if (cluster == null) { + return; + } + //1.get all available registries + GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix(cluster)).build(); + GetResponse getResponse = getClient().getKVClient().get(buildRegistryKeyPrefix(cluster), getOption).get(); + //2.add to list + List instanceList = getResponse.getKvs().stream().map(keyValue -> { + String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":"); + return new InetSocketAddress(instanceInfo[0], Integer.parseInt(instanceInfo[1])); + }).collect(Collectors.toList()); + clusterAddressMap.put(cluster, new Pair<>(getResponse.getHeader().getRevision(), instanceList)); + } + + /** + * get client + * + * @return client + */ + private Client getClient() { + if (client == null) { + synchronized (EtcdRegistryServiceImpl.class) { + if (client == null) { + String testEndpoint = System.getProperty(TEST_ENDPONT); + if (StringUtils.isNotBlank(testEndpoint)) { + client = Client.builder().endpoints(testEndpoint).build(); + } else { + client = Client.builder().endpoints(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY)).build(); + } + } + } + } + return client; + } + + /** + * get cluster name + * + * @return + */ + private String getClusterName() { + String clusterConfigName = String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_CLUSTER); + return FILE_CONFIG.getConfig(clusterConfigName, DEFAULT_CLUSTER_NAME); + } + + /** + * create a new lease id or return a existing lease id + */ + private long getLeaseId() throws Exception { + if (0 == leaseId) { + //create a new lease + leaseId = getClient().getLeaseClient().grant(TTL).get().getID(); + lifeKeeper = new EtcdLifeKeeper(leaseId); + lifeKeeperFuture = executorService.submit(lifeKeeper); + } + return leaseId; + } + + /** + * build registry key + * + * @return registry key + */ + private ByteSequence buildRegistryKey(InetSocketAddress address) { + return ByteSequence.from(REGISTRY_KEY_PREFIX + getClusterName() + "-" + NetUtil.toStringAddress(address), UTF_8); + } + + /** + * build registry key prefix + * + * @return registry key prefix + */ + private ByteSequence buildRegistryKeyPrefix(String cluster) { + return ByteSequence.from(REGISTRY_KEY_PREFIX + cluster, UTF_8); + } + + /** + * build registry value + * + * @param address + * @return registry value + */ + private ByteSequence buildRegistryValue(InetSocketAddress address) { + return ByteSequence.from(NetUtil.toStringAddress(address), UTF_8); + } + + /** + * the type etcd life keeper + */ + private class EtcdLifeKeeper implements Callable { + private final long leaseId; + private final Lease leaseClient; + private boolean running; + + + public EtcdLifeKeeper(long leaseId) { + this.leaseClient = getClient().getLeaseClient(); + this.leaseId = leaseId; + this.running = true; + + } + + /** + * process + */ + private void process() { + for (; ; ) { + try { + //1.get TTL + LeaseTimeToLiveResponse leaseTimeToLiveResponse = this.leaseClient.timeToLive(this.leaseId, LeaseOption.DEFAULT).get(); + final long tTl = leaseTimeToLiveResponse.getTTl(); + if (tTl <= LIFE_KEEP_CRITICAL) { + //2.refresh the TTL + this.leaseClient.keepAliveOnce(this.leaseId).get(); + } + TimeUnit.SECONDS.sleep(LIFE_KEEP_INTERVAL); + } catch (Exception e) { + LOGGER.error("EtcdLifeKeeper", e); + throw new ShouldNeverHappenException("failed to renewal the lease."); + } + } + } + + /** + * stop this task + */ + public void stop() { + this.running = false; + } + + @Override + public Boolean call() { + if (this.running) { + process(); + } + return this.running; + } + } + + /** + * the type etcd watcher + */ + private class EtcdWatcher implements Runnable { + private final Watch.Listener listener; + private Watch.Watcher watcher; + private String cluster; + + public EtcdWatcher(String cluster, Watch.Listener listener) { + this.cluster = cluster; + this.listener = listener; + } + + @Override + public void run() { + Watch watchClient = getClient().getWatchClient(); + WatchOption.Builder watchOptionBuilder = WatchOption.newBuilder().withPrefix(buildRegistryKeyPrefix(cluster)); + Pair> addressPair = clusterAddressMap.get(cluster); + if (Objects.nonNull(addressPair)) { + // Maybe addressPair isn't newest now, but it's ok + watchOptionBuilder.withRevision(addressPair.getKey()); + } + this.watcher = watchClient.watch(buildRegistryKeyPrefix(cluster), watchOptionBuilder.build(), this.listener); + } + + /** + * stop this task + */ + public void stop() { + this.watcher.close(); + } + } + + private static class Pair { + + /** + * Key of this Pair. + */ + private K key; + + /** + * Value of this this Pair. + */ + private V value; + + /** + * Creates a new pair + * @param key The key for this pair + * @param value The value to use for this pair + */ + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Gets the key for this pair. + * @return key for this pair + */ + public K getKey() { return key; } + + /** + * Gets the value for this pair. + * @return value for this pair + */ + public V getValue() { return value; } + } +} diff --git a/discovery/seata-discovery-etcd3/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-etcd3/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..e193c9f --- /dev/null +++ b/discovery/seata-discovery-etcd3/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.etcd3.EtcdRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryProviderTest.java b/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryProviderTest.java new file mode 100644 index 0000000..8d5fa61 --- /dev/null +++ b/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryProviderTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.etcd; + +import io.seata.discovery.registry.etcd3.EtcdRegistryProvider; +import io.seata.discovery.registry.etcd3.EtcdRegistryServiceImpl; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author xingfudeshi@gmail.com + * the type etcd registry provider test + */ +public class EtcdRegistryProviderTest { + /** + * test provide + */ + @Test + public void testProvide() { + assertThat(new EtcdRegistryProvider().provide()).isInstanceOf(EtcdRegistryServiceImpl.class); + } +} diff --git a/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryServiceImplTest.java b/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryServiceImplTest.java new file mode 100644 index 0000000..7eecff8 --- /dev/null +++ b/discovery/seata-discovery-etcd3/src/test/java/io/seata/discovery/registry/etcd/EtcdRegistryServiceImplTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.etcd; + +import io.etcd.jetcd.ByteSequence; +import io.etcd.jetcd.Client; +import io.etcd.jetcd.Watch; +import io.etcd.jetcd.launcher.junit.EtcdClusterResource; +import io.etcd.jetcd.options.DeleteOption; +import io.etcd.jetcd.options.GetOption; +import io.etcd.jetcd.watch.WatchResponse; +import io.seata.discovery.registry.etcd3.EtcdRegistryProvider; +import io.seata.discovery.registry.etcd3.EtcdRegistryServiceImpl; +import io.seata.discovery.registry.RegistryService; +import org.junit.Rule; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static io.netty.util.CharsetUtil.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author xingfudeshi@gmail.com + */ +@Disabled +public class EtcdRegistryServiceImplTest { + private static final String REGISTRY_KEY_PREFIX = "registry-seata-"; + private static final String CLUSTER_NAME = "default"; + @Rule + private final static EtcdClusterResource etcd = new EtcdClusterResource(CLUSTER_NAME, 1); + + private final Client client = Client.builder().endpoints(etcd.cluster().getClientEndpoints()).build(); + private final static String HOST = "127.0.0.1"; + private final static int PORT = 8091; + + @BeforeAll + public static void beforeClass() throws Exception { + System.setProperty(EtcdRegistryServiceImpl.TEST_ENDPONT, etcd.cluster().getClientEndpoints().get(0).toString()); + } + + @AfterAll + public static void afterClass() throws Exception { + System.setProperty(EtcdRegistryServiceImpl.TEST_ENDPONT, ""); + } + + @Test + public void testRegister() throws Exception { + RegistryService registryService = new EtcdRegistryProvider().provide(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT); + //1.register + registryService.register(inetSocketAddress); + //2.get instance information + GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build(); + long count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> { + String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":"); + return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]); + }).count(); + assertThat(count).isEqualTo(1); + } + + + @Test + public void testUnregister() throws Exception { + RegistryService registryService = new EtcdRegistryProvider().provide(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT); + //1.register + registryService.register(inetSocketAddress); + //2.get instance information + GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build(); + long count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> { + String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":"); + return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]); + }).count(); + assertThat(count).isEqualTo(1); + //3.unregister + registryService.unregister(inetSocketAddress); + //4.again get instance information + getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build(); + count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> { + String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":"); + return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]); + }).count(); + assertThat(count).isEqualTo(0); + + + } + + @Test + public void testSubscribe() throws Exception { + RegistryService registryService = new EtcdRegistryProvider().provide(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT); + //1.register + registryService.register(inetSocketAddress); + //2.subscribe + EtcdListener etcdListener = new EtcdListener(); + registryService.subscribe(CLUSTER_NAME, etcdListener); + //3.delete instance,see if the listener can be notified + DeleteOption deleteOption = DeleteOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build(); + client.getKVClient().delete(buildRegistryKeyPrefix(), deleteOption).get(); + assertThat(etcdListener.isNotified()).isTrue(); + } + + @Test + public void testUnsubscribe() throws Exception { + RegistryService registryService = new EtcdRegistryProvider().provide(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT); + //1.register + registryService.register(inetSocketAddress); + //2.subscribe + EtcdListener etcdListener = new EtcdListener(); + registryService.subscribe(CLUSTER_NAME, etcdListener); + //3.delete instance,see if the listener can be notified + DeleteOption deleteOption = DeleteOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build(); + client.getKVClient().delete(buildRegistryKeyPrefix(), deleteOption).get(); + assertThat(etcdListener.isNotified()).isTrue(); + //4.unsubscribe + registryService.unsubscribe(CLUSTER_NAME, etcdListener); + //5.reset + etcdListener.reset(); + //6.put instance,the listener should not be notified + client.getKVClient().put(buildRegistryKeyPrefix(), ByteSequence.from("test", UTF_8)).get(); + assertThat(etcdListener.isNotified()).isFalse(); + } + + @Test + public void testLookup() throws Exception { + RegistryService registryService = new EtcdRegistryProvider().provide(); + InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT); + //1.register + registryService.register(inetSocketAddress); + //2.lookup + List inetSocketAddresses = registryService.lookup("my_test_tx_group"); + assertThat(inetSocketAddresses).size().isEqualTo(1); + } + + /** + * build registry key prefix + * + * @return + */ + private ByteSequence buildRegistryKeyPrefix() { + return ByteSequence.from(REGISTRY_KEY_PREFIX, UTF_8); + } + + /** + * etcd listener + */ + private static class EtcdListener implements Watch.Listener { + private boolean notified = false; + + @Override + public void onNext(WatchResponse response) { + notified = true; + + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onCompleted() { + + } + + /** + * @return + */ + public boolean isNotified() throws InterruptedException { + TimeUnit.SECONDS.sleep(3); + return notified; + } + + /** + * reset + */ + private void reset() { + this.notified = false; + } + } +} diff --git a/discovery/seata-discovery-eureka/pom.xml b/discovery/seata-discovery-eureka/pom.xml new file mode 100644 index 0000000..99c7a8d --- /dev/null +++ b/discovery/seata-discovery-eureka/pom.xml @@ -0,0 +1,47 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-eureka + seata-discovery-eureka ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + com.netflix.eureka + eureka-client + + + com.netflix.archaius + archaius-core + + + javax.inject + javax.inject + + + diff --git a/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/CustomEurekaInstanceConfig.java b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/CustomEurekaInstanceConfig.java new file mode 100644 index 0000000..3e27e50 --- /dev/null +++ b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/CustomEurekaInstanceConfig.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.eureka; + +import io.seata.common.util.StringUtils; +import com.netflix.appinfo.EurekaInstanceConfig; +import com.netflix.appinfo.MyDataCenterInstanceConfig; + +/** + * @author: rui_849217@163.com + * override MyDataCenterInstanceConfig for set value, + * eg: instanceId \ipAddress \ applicationName... + */ +public class CustomEurekaInstanceConfig extends MyDataCenterInstanceConfig implements EurekaInstanceConfig { + private String applicationName; + private String instanceId; + private String ipAddress; + private int port = -1; + + @Override + public String getInstanceId() { + if (StringUtils.isBlank(instanceId)) { + return super.getInstanceId(); + } + return instanceId; + } + + @Override + public String getIpAddress() { + if (StringUtils.isBlank(ipAddress)) { + return super.getIpAddress(); + } + return ipAddress; + } + + @Override + public int getNonSecurePort() { + if (port == -1) { + return super.getNonSecurePort(); + } + return port; + } + + @Override + public String getAppname() { + if (StringUtils.isBlank(applicationName)) { + return super.getAppname(); + } + return applicationName; + } + + @Override + public String getHostName(boolean refresh) { + return this.getIpAddress(); + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public void setPort(int port) { + this.port = port; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } +} diff --git a/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryProvider.java b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryProvider.java new file mode 100644 index 0000000..d55407f --- /dev/null +++ b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.eureka; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryService; +import io.seata.discovery.registry.RegistryProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Eureka", order = 1) +public class EurekaRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return EurekaRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java new file mode 100644 index 0000000..8bff9b3 --- /dev/null +++ b/discovery/seata-discovery-eureka/src/main/java/io/seata/discovery/registry/eureka/EurekaRegistryServiceImpl.java @@ -0,0 +1,260 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.eureka; + +import com.netflix.appinfo.ApplicationInfoManager; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider; +import com.netflix.config.ConfigurationManager; +import com.netflix.discovery.DefaultEurekaClientConfig; +import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.EurekaClient; +import com.netflix.discovery.EurekaEventListener; +import com.netflix.discovery.shared.Application; +import io.seata.common.exception.EurekaRegistryException; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; + +/** + * The type Eureka registry service. + * + * @author: rui_849217@163.com + */ +public class EurekaRegistryServiceImpl implements RegistryService { + private static final Logger LOGGER = LoggerFactory.getLogger(EurekaRegistryServiceImpl.class); + + private static final String DEFAULT_APPLICATION = "default"; + private static final String PRO_SERVICE_URL_KEY = "serviceUrl"; + private static final String FILE_ROOT_REGISTRY = "registry"; + private static final String FILE_CONFIG_SPLIT_CHAR = "."; + private static final String REGISTRY_TYPE = "eureka"; + private static final String CLUSTER = "application"; + private static final String REGISTRY_WEIGHT = "weight"; + private static final String EUREKA_CONFIG_SERVER_URL_KEY = "eureka.serviceUrl.default"; + private static final String EUREKA_CONFIG_REFRESH_KEY = "eureka.client.refresh.interval"; + private static final String EUREKA_CONFIG_SHOULD_REGISTER = "eureka.registration.enabled"; + private static final String EUREKA_CONFIG_METADATA_WEIGHT = "eureka.metadata.weight"; + private static final int EUREKA_REFRESH_INTERVAL = 5; + private static final int MAP_INITIAL_CAPACITY = 8; + private static final String DEFAULT_WEIGHT = "1"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static ConcurrentMap> clusterAddressMap; + + private static volatile boolean subscribeListener = false; + private static volatile ApplicationInfoManager applicationInfoManager; + private static volatile CustomEurekaInstanceConfig instanceConfig; + private static volatile EurekaRegistryServiceImpl instance; + private static volatile EurekaClient eurekaClient; + + private EurekaRegistryServiceImpl() { + } + + static EurekaRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (EurekaRegistryServiceImpl.class) { + if (instance == null) { + clusterAddressMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + instanceConfig = new CustomEurekaInstanceConfig(); + instance = new EurekaRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + instanceConfig.setIpAddress(address.getAddress().getHostAddress()); + instanceConfig.setPort(address.getPort()); + instanceConfig.setApplicationName(getApplicationName()); + instanceConfig.setInstanceId(getInstanceId()); + getEurekaClient(true); + applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.UP); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + if (eurekaClient == null) { + return; + } + applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.DOWN); + } + + @Override + public void subscribe(String cluster, EurekaEventListener listener) throws Exception { + subscribeListener = true; + getEurekaClient(false).registerEventListener(listener); + } + + @Override + public void unsubscribe(String cluster, EurekaEventListener listener) throws Exception { + subscribeListener = false; + getEurekaClient(false).unregisterEventListener(listener); + } + + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + if (!subscribeListener) { + refreshCluster(); + subscribe(null, event -> { + try { + refreshCluster(); + } catch (Exception e) { + LOGGER.error("Eureka event listener refreshCluster error:{}", e.getMessage(), e); + } + }); + } + return new ArrayList<>(clusterAddressMap.getOrDefault(clusterName.toUpperCase(), Collections.emptySet())); + } + + @Override + public void close() throws Exception { + if (eurekaClient != null) { + eurekaClient.shutdown(); + } + clean(); + } + + private void refreshCluster() { + List applications = getEurekaClient(false).getApplications().getRegisteredApplications(); + + if (CollectionUtils.isEmpty(applications)) { + clusterAddressMap.clear(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refreshCluster success, cluster empty!"); + } + return; + } + + ConcurrentMap> collect = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + + for (Application application : applications) { + List instances = application.getInstances(); + + if (CollectionUtils.isNotEmpty(instances)) { + Set addressSet = instances.stream() + .map(instance -> new InetSocketAddress(instance.getIPAddr(), instance.getPort())) + .collect(Collectors.toSet()); + collect.put(application.getName(), addressSet); + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refreshCluster success, cluster: " + collect); + } + + clusterAddressMap = collect; + } + + private Properties getEurekaProperties(boolean needRegister) { + Properties eurekaProperties = new Properties(); + eurekaProperties.setProperty(EUREKA_CONFIG_REFRESH_KEY, String.valueOf(EUREKA_REFRESH_INTERVAL)); + + String url = FILE_CONFIG.getConfig(getEurekaServerUrlFileKey()); + if (StringUtils.isBlank(url)) { + throw new EurekaRegistryException("eureka server url can not be null!"); + } + eurekaProperties.setProperty(EUREKA_CONFIG_SERVER_URL_KEY, url); + + String weight = FILE_CONFIG.getConfig(getEurekaInstanceWeightFileKey()); + if (StringUtils.isNotBlank(weight)) { + eurekaProperties.setProperty(EUREKA_CONFIG_METADATA_WEIGHT, weight); + } else { + eurekaProperties.setProperty(EUREKA_CONFIG_METADATA_WEIGHT, DEFAULT_WEIGHT); + } + + if (!needRegister) { + eurekaProperties.setProperty(EUREKA_CONFIG_SHOULD_REGISTER, "false"); + } + + return eurekaProperties; + } + + private String getApplicationName() { + String application = FILE_CONFIG.getConfig(getEurekaApplicationFileKey()); + if (application == null) { + application = DEFAULT_APPLICATION; + } + return application; + } + + private EurekaClient getEurekaClient(boolean needRegister) throws EurekaRegistryException { + if (eurekaClient == null) { + synchronized (EurekaRegistryServiceImpl.class) { + try { + if (eurekaClient == null) { + if (!needRegister) { + instanceConfig = new CustomEurekaInstanceConfig(); + } + ConfigurationManager.loadProperties(getEurekaProperties(needRegister)); + InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get(); + applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo); + eurekaClient = new DiscoveryClient(applicationInfoManager, new DefaultEurekaClientConfig()); + } + } catch (Exception e) { + clean(); + throw new EurekaRegistryException("register eureka is error!", e); + } + } + } + return eurekaClient; + } + + private void clean() { + eurekaClient = null; + applicationInfoManager = null; + instanceConfig = null; + } + + private String getInstanceId() { + return String.format("%s:%s:%d", instanceConfig.getIpAddress(), instanceConfig.getAppname(), + instanceConfig.getNonSecurePort()); + } + + private String getEurekaServerUrlFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_SERVICE_URL_KEY); + } + + private String getEurekaApplicationFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, CLUSTER); + } + + private String getEurekaInstanceWeightFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_WEIGHT); + } +} diff --git a/discovery/seata-discovery-eureka/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-eureka/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..f2e7445 --- /dev/null +++ b/discovery/seata-discovery-eureka/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.eureka.EurekaRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-nacos/pom.xml b/discovery/seata-discovery-nacos/pom.xml new file mode 100644 index 0000000..39093c0 --- /dev/null +++ b/discovery/seata-discovery-nacos/pom.xml @@ -0,0 +1,39 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-nacos + seata-discovery-nacos ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + com.alibaba.nacos + nacos-client + + + diff --git a/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryProvider.java b/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryProvider.java new file mode 100644 index 0000000..c07e0ce --- /dev/null +++ b/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.nacos; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryService; +import io.seata.discovery.registry.RegistryProvider; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Nacos", order = 1) +public class NacosRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return NacosRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java b/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java new file mode 100644 index 0000000..f77cc73 --- /dev/null +++ b/discovery/seata-discovery-nacos/src/main/java/io/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java @@ -0,0 +1,248 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.nacos; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.config.ConfigurationKeys; +import io.seata.discovery.registry.RegistryService; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; + +/** + * The type Nacos registry service. + * + * @author slievrly + */ +public class NacosRegistryServiceImpl implements RegistryService { + private static final String DEFAULT_NAMESPACE = ""; + private static final String DEFAULT_CLUSTER = "default"; + private static final String DEFAULT_GROUP = "DEFAULT_GROUP"; + private static final String DEFAULT_APPLICATION = "seata-server"; + private static final String PRO_SERVER_ADDR_KEY = "serverAddr"; + private static final String PRO_NAMESPACE_KEY = "namespace"; + private static final String REGISTRY_TYPE = "nacos"; + private static final String REGISTRY_CLUSTER = "cluster"; + private static final String PRO_APPLICATION_KEY = "application"; + private static final String PRO_GROUP_KEY = "group"; + private static final String USER_NAME = "username"; + private static final String PASSWORD = "password"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static volatile NamingService naming; + private static final ConcurrentMap> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>(); + private static final ConcurrentMap> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>(); + private static volatile NacosRegistryServiceImpl instance; + private static final Object LOCK_OBJ = new Object(); + + private NacosRegistryServiceImpl() { + } + + /** + * Gets instance. + * + * @return the instance + */ + static NacosRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (NacosRegistryServiceImpl.class) { + if (instance == null) { + instance = new NacosRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + getNamingInstance().registerInstance(getServiceName(), getServiceGroup(), address.getAddress().getHostAddress(), address.getPort(), getClusterName()); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + getNamingInstance().deregisterInstance(getServiceName(), getServiceGroup(), address.getAddress().getHostAddress(), address.getPort(), getClusterName()); + } + + @Override + public void subscribe(String cluster, EventListener listener) throws Exception { + List clusters = new ArrayList<>(); + clusters.add(cluster); + LISTENER_SERVICE_MAP.computeIfAbsent(cluster, key -> new ArrayList<>()) + .add(listener); + getNamingInstance().subscribe(getServiceName(), getServiceGroup(), clusters, listener); + } + + @Override + public void unsubscribe(String cluster, EventListener listener) throws Exception { + List clusters = new ArrayList<>(); + clusters.add(cluster); + List subscribeList = LISTENER_SERVICE_MAP.get(cluster); + if (subscribeList != null) { + List newSubscribeList = subscribeList.stream() + .filter(eventListener -> !eventListener.equals(listener)) + .collect(Collectors.toList()); + LISTENER_SERVICE_MAP.put(cluster, newSubscribeList); + } + getNamingInstance().unsubscribe(getServiceName(), getServiceGroup(), clusters, listener); + } + + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) { + synchronized (LOCK_OBJ) { + if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) { + List clusters = new ArrayList<>(); + clusters.add(clusterName); + List firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters); + if (null != firstAllInstances) { + List newAddressList = firstAllInstances.stream() + .filter(instance -> instance.isEnabled() && instance.isHealthy()) + .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort())) + .collect(Collectors.toList()); + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); + } + subscribe(clusterName, event -> { + List instances = ((NamingEvent) event).getInstances(); + if (null == instances && null != CLUSTER_ADDRESS_MAP.get(clusterName)) { + CLUSTER_ADDRESS_MAP.remove(clusterName); + } else if (!CollectionUtils.isEmpty(instances)) { + List newAddressList = instances.stream() + .filter(instance -> instance.isEnabled() && instance.isHealthy()) + .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort())) + .collect(Collectors.toList()); + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); + } + }); + } + } + } + return CLUSTER_ADDRESS_MAP.get(clusterName); + } + + @Override + public void close() throws Exception { + + } + + /** + * Gets naming instance. + * + * @return the naming instance + * @throws Exception the exception + */ + public static NamingService getNamingInstance() throws Exception { + if (naming == null) { + synchronized (NacosRegistryServiceImpl.class) { + if (naming == null) { + naming = NacosFactory.createNamingService(getNamingProperties()); + } + } + } + return naming; + } + + private static Properties getNamingProperties() { + Properties properties = new Properties(); + if (System.getProperty(PRO_SERVER_ADDR_KEY) != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, System.getProperty(PRO_SERVER_ADDR_KEY)); + } else { + String address = FILE_CONFIG.getConfig(getNacosAddrFileKey()); + if (address != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, address); + } + } + if (System.getProperty(PRO_NAMESPACE_KEY) != null) { + properties.setProperty(PRO_NAMESPACE_KEY, System.getProperty(PRO_NAMESPACE_KEY)); + } else { + String namespace = FILE_CONFIG.getConfig(getNacosNameSpaceFileKey()); + if (namespace == null) { + namespace = DEFAULT_NAMESPACE; + } + properties.setProperty(PRO_NAMESPACE_KEY, namespace); + } + String userName = StringUtils.isNotBlank(System.getProperty(USER_NAME)) ? System.getProperty(USER_NAME) + : FILE_CONFIG.getConfig(getNacosUserName()); + if (StringUtils.isNotBlank(userName)) { + String password = StringUtils.isNotBlank(System.getProperty(PASSWORD)) ? System.getProperty(PASSWORD) + : FILE_CONFIG.getConfig(getNacosPassword()); + if (StringUtils.isNotBlank(password)) { + properties.setProperty(USER_NAME, userName); + properties.setProperty(PASSWORD, password); + } + } + return properties; + } + + private static String getClusterName() { + return FILE_CONFIG.getConfig(getNacosClusterFileKey(), DEFAULT_CLUSTER); + } + + private static String getServiceName() { + return FILE_CONFIG.getConfig(getNacosApplicationFileKey(), DEFAULT_APPLICATION); + } + + private static String getServiceGroup() { + return FILE_CONFIG.getConfig(getNacosApplicationGroupKey(), DEFAULT_GROUP); + } + + private static String getNacosAddrFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_SERVER_ADDR_KEY); + } + + private static String getNacosNameSpaceFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_NAMESPACE_KEY); + } + + private static String getNacosClusterFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_CLUSTER); + } + + private static String getNacosApplicationFileKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_APPLICATION_KEY); + } + + private static String getNacosApplicationGroupKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_GROUP_KEY); + } + + private static String getNacosUserName() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, USER_NAME); + } + + private static String getNacosPassword() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY, REGISTRY_TYPE, PASSWORD); + } +} diff --git a/discovery/seata-discovery-nacos/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-nacos/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..9d32d59 --- /dev/null +++ b/discovery/seata-discovery-nacos/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.nacos.NacosRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-redis/pom.xml b/discovery/seata-discovery-redis/pom.xml new file mode 100644 index 0000000..d743921 --- /dev/null +++ b/discovery/seata-discovery-redis/pom.xml @@ -0,0 +1,39 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-redis + seata-discovery-redis ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + redis.clients + jedis + + + diff --git a/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisListener.java b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisListener.java new file mode 100644 index 0000000..a6e6635 --- /dev/null +++ b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisListener.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.redis; + +/** + * The RedisListener + * + * @author kl @kailing.pub + */ +public interface RedisListener { + /** + * The constant REGISTER. + */ + String REGISTER = "register"; + /** + * The constant UN_REGISTER. + */ + String UN_REGISTER = "unregister"; + + /** + * use for redis event + * + * @param event the event + */ + void onEvent(String event); +} diff --git a/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryProvider.java b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryProvider.java new file mode 100644 index 0000000..f7edd58 --- /dev/null +++ b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.redis; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "Redis", order = 1) +public class RedisRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return RedisRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryServiceImpl.java b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryServiceImpl.java new file mode 100644 index 0000000..7342ff2 --- /dev/null +++ b/discovery/seata-discovery-redis/src/main/java/io/seata/discovery/registry/redis/RedisRegistryServiceImpl.java @@ -0,0 +1,259 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.redis; + +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPubSub; +import redis.clients.jedis.Protocol; + +/** + * The type Redis registry service. + * + * @author kl @kailing.pub + */ +public class RedisRegistryServiceImpl implements RegistryService { + + private static final Logger LOGGER = LoggerFactory.getLogger(RedisRegistryServiceImpl.class); + private static final String PRO_SERVER_ADDR_KEY = "serverAddr"; + private static final String REDIS_FILEKEY_PREFIX = "registry.redis."; + private static final String DEFAULT_CLUSTER = "default"; + private static final String REGISTRY_CLUSTER_KEY = "cluster"; + private String clusterName; + private static final String REDIS_DB = "db"; + private static final String REDIS_PASSWORD = "password"; + private static final ConcurrentMap> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>(); + private static final ConcurrentMap> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>(); + private static volatile RedisRegistryServiceImpl instance; + private static volatile JedisPool jedisPool; + + private ExecutorService threadPoolExecutor = new ScheduledThreadPoolExecutor(1, + new NamedThreadFactory("RedisRegistryService", 1)); + + private RedisRegistryServiceImpl() { + Configuration seataConfig = ConfigurationFactory.CURRENT_FILE_INSTANCE; + this.clusterName = seataConfig.getConfig(REDIS_FILEKEY_PREFIX + REGISTRY_CLUSTER_KEY, DEFAULT_CLUSTER); + String password = seataConfig.getConfig(getRedisPasswordFileKey()); + String serverAddr = seataConfig.getConfig(getRedisAddrFileKey()); + String[] serverArr = serverAddr.split(":"); + String host = serverArr[0]; + int port = Integer.parseInt(serverArr[1]); + int db = seataConfig.getInt(getRedisDbFileKey()); + GenericObjectPoolConfig redisConfig = new GenericObjectPoolConfig(); + redisConfig.setTestOnBorrow(seataConfig.getBoolean(REDIS_FILEKEY_PREFIX + "test.on.borrow", true)); + redisConfig.setTestOnReturn(seataConfig.getBoolean(REDIS_FILEKEY_PREFIX + "test.on.return", false)); + redisConfig.setTestWhileIdle(seataConfig.getBoolean(REDIS_FILEKEY_PREFIX + "test.while.idle", false)); + int maxIdle = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "max.idle", 0); + if (maxIdle > 0) { + redisConfig.setMaxIdle(maxIdle); + } + int minIdle = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "min.idle", 0); + if (minIdle > 0) { + redisConfig.setMinIdle(minIdle); + } + int maxActive = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "max.active", 0); + if (maxActive > 0) { + redisConfig.setMaxTotal(maxActive); + } + int maxTotal = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "max.total", 0); + if (maxTotal > 0) { + redisConfig.setMaxTotal(maxTotal); + } + int maxWait = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "max.wait", + seataConfig.getInt(REDIS_FILEKEY_PREFIX + "timeout", 0)); + if (maxWait > 0) { + redisConfig.setMaxWaitMillis(maxWait); + } + int numTestsPerEvictionRun = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "num.tests.per.eviction.run", 0); + if (numTestsPerEvictionRun > 0) { + redisConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun); + } + int timeBetweenEvictionRunsMillis = seataConfig.getInt( + REDIS_FILEKEY_PREFIX + "time.between.eviction.runs.millis", 0); + if (timeBetweenEvictionRunsMillis > 0) { + redisConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + } + int minEvictableIdleTimeMillis = seataConfig.getInt(REDIS_FILEKEY_PREFIX + "min.evictable.idle.time.millis", + 0); + if (minEvictableIdleTimeMillis > 0) { + redisConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + } + if (StringUtils.isNullOrEmpty(password)) { + jedisPool = new JedisPool(redisConfig, host, port, Protocol.DEFAULT_TIMEOUT, null, db); + } else { + jedisPool = new JedisPool(redisConfig, host, port, Protocol.DEFAULT_TIMEOUT, password, db); + } + } + + /** + * Gets instance. + * + * @return the instance + */ + static RedisRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (RedisRegistryServiceImpl.class) { + if (instance == null) { + instance = new RedisRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) { + NetUtil.validAddress(address); + String serverAddr = NetUtil.toStringAddress(address); + try (Jedis jedis = jedisPool.getResource()) { + jedis.hset(getRedisRegistryKey(), serverAddr, ManagementFactory.getRuntimeMXBean().getName()); + jedis.publish(getRedisRegistryKey(), serverAddr + "-" + RedisListener.REGISTER); + } + } + + @Override + public void unregister(InetSocketAddress address) { + NetUtil.validAddress(address); + String serverAddr = NetUtil.toStringAddress(address); + try (Jedis jedis = jedisPool.getResource()) { + jedis.hdel(getRedisRegistryKey(), serverAddr); + jedis.publish(getRedisRegistryKey(), serverAddr + "-" + RedisListener.UN_REGISTER); + } + } + + @Override + public void subscribe(String cluster, RedisListener listener) { + String redisRegistryKey = REDIS_FILEKEY_PREFIX + cluster; + LISTENER_SERVICE_MAP.computeIfAbsent(cluster, key -> new ArrayList<>()) + .add(listener); + threadPoolExecutor.submit(() -> { + try { + try (Jedis jedis = jedisPool.getResource()) { + jedis.subscribe(new NotifySub(LISTENER_SERVICE_MAP.get(cluster)), redisRegistryKey); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + }); + } + + @Override + public void unsubscribe(String cluster, RedisListener listener) { + } + + @Override + public List lookup(String key) { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) { + String redisRegistryKey = REDIS_FILEKEY_PREFIX + clusterName; + Map instances; + try (Jedis jedis = jedisPool.getResource()) { + instances = jedis.hgetAll(redisRegistryKey); + } + if (instances != null && !instances.isEmpty()) { + Set newAddressSet = instances.keySet().stream() + .map(NetUtil::toInetSocketAddress) + .collect(Collectors.toSet()); + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressSet); + } + subscribe(clusterName, msg -> { + String[] msgr = msg.split("-"); + String serverAddr = msgr[0]; + String eventType = msgr[1]; + switch (eventType) { + case RedisListener.REGISTER: + CLUSTER_ADDRESS_MAP.get(clusterName).add(NetUtil.toInetSocketAddress(serverAddr)); + break; + case RedisListener.UN_REGISTER: + CLUSTER_ADDRESS_MAP.get(clusterName).remove(NetUtil.toInetSocketAddress(serverAddr)); + break; + default: + throw new ShouldNeverHappenException("unknown redis msg:" + msg); + } + }); + } + return new ArrayList<>(CLUSTER_ADDRESS_MAP.getOrDefault(clusterName, Collections.emptySet())); + } + + @Override + public void close() throws Exception { + jedisPool.destroy(); + } + + private static class NotifySub extends JedisPubSub { + + private final List redisListeners; + + /** + * Instantiates a new Notify sub. + * + * @param redisListeners the redis listeners + */ + NotifySub(List redisListeners) { + this.redisListeners = redisListeners; + } + + @Override + public void onMessage(String key, String msg) { + for (RedisListener listener : redisListeners) { + listener.onEvent(msg); + } + } + } + + private String getRedisRegistryKey() { + return REDIS_FILEKEY_PREFIX + clusterName; + } + + private String getRedisAddrFileKey() { + return REDIS_FILEKEY_PREFIX + PRO_SERVER_ADDR_KEY; + } + + private String getRedisPasswordFileKey() { + return REDIS_FILEKEY_PREFIX + REDIS_PASSWORD; + } + + private String getRedisDbFileKey() { + return REDIS_FILEKEY_PREFIX + REDIS_DB; + } + +} diff --git a/discovery/seata-discovery-redis/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-redis/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..419a777 --- /dev/null +++ b/discovery/seata-discovery-redis/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.redis.RedisRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-sofa/pom.xml b/discovery/seata-discovery-sofa/pom.xml new file mode 100644 index 0000000..a3d7221 --- /dev/null +++ b/discovery/seata-discovery-sofa/pom.xml @@ -0,0 +1,44 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-sofa + seata-discovery-sofa ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + com.alipay.sofa + registry-client-all + + + com.alipay.sofa + registry-test + test + + + diff --git a/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryProvider.java b/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryProvider.java new file mode 100644 index 0000000..7b67e56 --- /dev/null +++ b/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.sofa; + + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author leizhiyuan + */ +@LoadLevel(name = "Sofa", order = 1) +public class SofaRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return SofaRegistryServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryServiceImpl.java b/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryServiceImpl.java new file mode 100644 index 0000000..ed095fc --- /dev/null +++ b/discovery/seata-discovery-sofa/src/main/java/io/seata/discovery/registry/sofa/SofaRegistryServiceImpl.java @@ -0,0 +1,302 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.sofa; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.alipay.sofa.registry.client.api.RegistryClient; +import com.alipay.sofa.registry.client.api.RegistryClientConfig; +import com.alipay.sofa.registry.client.api.SubscriberDataObserver; +import com.alipay.sofa.registry.client.api.model.RegistryType; +import com.alipay.sofa.registry.client.api.registration.PublisherRegistration; +import com.alipay.sofa.registry.client.api.registration.SubscriberRegistration; +import com.alipay.sofa.registry.client.provider.DefaultRegistryClient; +import com.alipay.sofa.registry.client.provider.DefaultRegistryClientConfigBuilder; +import com.alipay.sofa.registry.core.model.ScopeEnum; +import io.seata.common.util.NetUtil; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.apache.commons.lang.StringUtils; + +import static io.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; +import static io.seata.config.ConfigurationKeys.FILE_ROOT_REGISTRY; + +/** + * The type SOFARegistry registry service. + * + * @author leizhiyuan + */ +public class SofaRegistryServiceImpl implements RegistryService { + + private static final String SOFA_FILEKEY_PREFIX = "registry.sofa."; + + private static final String PRO_SERVER_ADDR_KEY = "serverAddr"; + private static final String PRO_REGION_KEY = "region"; + private static final String PRO_DATACENTER_KEY = "datacenter"; + private static final String PRO_GROUP_KEY = "group"; + private static final String PRO_APPLICATION_KEY = "application"; + private static final String PRO_CLUSTER_KEY = "cluster"; + private static final String PRO_ADDRESS_WAIT_TIME_KEY = "addressWaitTime"; + + private static final String DEFAULT_LOCAL_DATACENTER = "DefaultDataCenter"; + private static final String DEFAULT_LOCAL_REGION = "DEFAULT_ZONE"; + private static final String DEFAULT_GROUP = "SEATA_GROUP"; + private static final String DEFAULT_APPLICATION = "default"; + private static final String DEFAULT_CLUSTER = "default"; + private static final String DEFAULT_ADDRESS_WAIT_TIME = "3000"; + + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + private static final String HOST_SEPERATOR = ":"; + private static final String REGISTRY_TYPE = "sofa"; + + private static final ConcurrentMap> LISTENER_SERVICE_MAP + = new ConcurrentHashMap<>(); + private static final ConcurrentMap> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>(); + private static Properties registryProps; + private static volatile RegistryClient registryClient; + + private static volatile SofaRegistryServiceImpl instance; + + private SofaRegistryServiceImpl() { + } + + /** + * Gets instance. + * + * @return the instance + */ + static SofaRegistryServiceImpl getInstance() { + if (instance == null) { + synchronized (SofaRegistryServiceImpl.class) { + if (instance == null) { + registryProps = getNamingProperties(); + instance = new SofaRegistryServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + String clusterName = registryProps.getProperty(PRO_CLUSTER_KEY); + PublisherRegistration publisherRegistration = new PublisherRegistration(clusterName); + publisherRegistration.setGroup(registryProps.getProperty(PRO_GROUP_KEY)); + String serviceData = address.getAddress().getHostAddress() + HOST_SEPERATOR + address.getPort(); + getRegistryInstance().register(publisherRegistration, serviceData); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + String clusterName = registryProps.getProperty(PRO_CLUSTER_KEY); + getRegistryInstance().unregister(clusterName, registryProps.getProperty(PRO_GROUP_KEY), RegistryType.PUBLISHER); + } + + private RegistryClient getRegistryInstance() { + if (registryClient == null) { + synchronized (SofaRegistryServiceImpl.class) { + if (registryClient == null) { + String address = registryProps.getProperty(PRO_SERVER_ADDR_KEY); + final String portStr = StringUtils.substringAfter(address, HOST_SEPERATOR); + + RegistryClientConfig config = DefaultRegistryClientConfigBuilder.start() + .setAppName(getApplicationName()) + .setDataCenter(registryProps.getProperty(PRO_DATACENTER_KEY)) + .setZone(registryProps.getProperty(PRO_REGION_KEY)) + .setRegistryEndpoint(StringUtils.substringBefore(address, HOST_SEPERATOR)) + .setRegistryEndpointPort(Integer.parseInt(portStr)).build(); + + DefaultRegistryClient result = new DefaultRegistryClient(config); + result.init(); + registryClient = result; + } + } + } + return registryClient; + } + + @Override + public void subscribe(String cluster, SubscriberDataObserver listener) throws Exception { + SubscriberRegistration subscriberRegistration = new SubscriberRegistration(cluster, listener); + subscriberRegistration.setScopeEnum(ScopeEnum.global); + subscriberRegistration.setGroup(registryProps.getProperty(PRO_GROUP_KEY)); + + LISTENER_SERVICE_MAP.computeIfAbsent(cluster, key -> new ArrayList<>()) + .add(listener); + getRegistryInstance().register(subscriberRegistration); + } + + @Override + public void unsubscribe(String cluster, SubscriberDataObserver listener) throws Exception { + getRegistryInstance().unregister(cluster, registryProps.getProperty(PRO_GROUP_KEY), RegistryType.SUBSCRIBER); + } + + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + if (clusterName == null) { + return null; + } + if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) { + CountDownLatch respondRegistries = new CountDownLatch(1); + subscribe(clusterName, (dataId, data) -> { + Map> instances = data.getZoneData(); + if (instances == null && CLUSTER_ADDRESS_MAP.get(clusterName) != null) { + CLUSTER_ADDRESS_MAP.remove(clusterName); + } else { + List newAddressList = flatData(instances); + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); + } + respondRegistries.countDown(); + }); + + //wait max for first lookup + final String property = registryProps.getProperty(PRO_ADDRESS_WAIT_TIME_KEY); + respondRegistries.await(Integer.parseInt(property), TimeUnit.MILLISECONDS); + + } + return CLUSTER_ADDRESS_MAP.get(clusterName); + } + + private List flatData(Map> instances) { + List result = new ArrayList<>(); + + for (Map.Entry> entry : instances.entrySet()) { + for (String str : entry.getValue()) { + String ip = StringUtils.substringBefore(str, HOST_SEPERATOR); + String port = StringUtils.substringAfter(str, HOST_SEPERATOR); + InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, Integer.parseInt(port)); + result.add(inetSocketAddress); + } + } + return result; + } + + @Override + public void close() throws Exception { + } + + private static Properties getNamingProperties() { + Properties properties = new Properties(); + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_SERVER_ADDR_KEY) != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_SERVER_ADDR_KEY)); + } else { + String address = FILE_CONFIG.getConfig(getSofaAddrFileKey()); + if (address != null) { + properties.setProperty(PRO_SERVER_ADDR_KEY, address); + } + } + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_REGION_KEY) != null) { + properties.setProperty(PRO_REGION_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_REGION_KEY)); + } else { + String region = FILE_CONFIG.getConfig(getSofaRegionFileKey()); + if (region == null) { + region = DEFAULT_LOCAL_REGION; + } + properties.setProperty(PRO_REGION_KEY, region); + } + + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_DATACENTER_KEY) != null) { + properties.setProperty(PRO_DATACENTER_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_DATACENTER_KEY)); + } else { + String datacenter = FILE_CONFIG.getConfig(getSofaDataCenterFileKey()); + if (datacenter == null) { + datacenter = DEFAULT_LOCAL_DATACENTER; + } + properties.setProperty(PRO_DATACENTER_KEY, datacenter); + } + + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_GROUP_KEY) != null) { + properties.setProperty(PRO_GROUP_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_GROUP_KEY)); + } else { + String group = FILE_CONFIG.getConfig(getSofaGroupFileKey()); + if (group == null) { + group = DEFAULT_GROUP; + } + properties.setProperty(PRO_GROUP_KEY, group); + } + + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_CLUSTER_KEY) != null) { + properties.setProperty(PRO_CLUSTER_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_CLUSTER_KEY)); + } else { + String cluster = FILE_CONFIG.getConfig(getSofaClusterFileKey()); + if (cluster == null) { + cluster = DEFAULT_CLUSTER; + } + properties.setProperty(PRO_CLUSTER_KEY, cluster); + } + + if (System.getProperty(SOFA_FILEKEY_PREFIX + PRO_ADDRESS_WAIT_TIME_KEY) != null) { + properties.setProperty(PRO_ADDRESS_WAIT_TIME_KEY, System.getProperty(SOFA_FILEKEY_PREFIX + PRO_ADDRESS_WAIT_TIME_KEY)); + } else { + String group = FILE_CONFIG.getConfig(getSofaAddressWaitTimeFileKey()); + if (group == null) { + group = DEFAULT_ADDRESS_WAIT_TIME; + } + properties.setProperty(PRO_ADDRESS_WAIT_TIME_KEY, group); + } + + return properties; + } + + private static String getSofaClusterFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_CLUSTER_KEY); + } + + private static String getSofaAddressWaitTimeFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_ADDRESS_WAIT_TIME_KEY); + } + + private static String getSofaAddrFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_SERVER_ADDR_KEY); + } + + private static String getSofaRegionFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_REGION_KEY); + } + + private static String getSofaDataCenterFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_DATACENTER_KEY); + } + + private static String getSofaGroupFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_GROUP_KEY); + } + + private String getApplicationFileKey() { + return String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, PRO_APPLICATION_KEY); + } + + private String getApplicationName() { + String application = FILE_CONFIG.getConfig(getApplicationFileKey()); + if (application == null) { + application = DEFAULT_APPLICATION; + } + return application; + } +} diff --git a/discovery/seata-discovery-sofa/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-sofa/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..a5afd86 --- /dev/null +++ b/discovery/seata-discovery-sofa/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.sofa.SofaRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-sofa/src/test/io/seata/discovery/registry/sofa/SofaRegistryServiceImplTest.java b/discovery/seata-discovery-sofa/src/test/io/seata/discovery/registry/sofa/SofaRegistryServiceImplTest.java new file mode 100644 index 0000000..2fcc937 --- /dev/null +++ b/discovery/seata-discovery-sofa/src/test/io/seata/discovery/registry/sofa/SofaRegistryServiceImplTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.discovery.registry.sofa; + +import com.alipay.sofa.registry.server.test.TestRegistryMain; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +/** + * The type SofaRegistryServiceImpl test. + * + * @author leizhiyuan + */ +public class SofaRegistryServiceImplTest { + + private static TestRegistryMain registryMain; + + @BeforeAll + public static void beforeClass() { + System.setProperty("serverAddr", "127.0.0.1:9603"); + System.setProperty("addressWaitTime", "10000"); + registryMain = new TestRegistryMain(); + try { + registryMain.startRegistry(); + } catch (Exception e) { + Assertions.fail("start sofaregistry fail"); + } + } + + @Test + public void testSofaRegistry() { + final InetSocketAddress address = new InetSocketAddress(1234); + + final SofaRegistryServiceImpl instance = SofaRegistryServiceImpl.getInstance(); + try { + instance.register(address); + } catch (Exception e) { + Assertions.fail(e.getMessage()); + } + + //need sofa registry to sync data + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + } + + List result = new ArrayList<>(); + try { + result = instance.lookup("my_test_tx_group"); + } catch (Exception e) { + Assertions.fail(e.getMessage()); + } + + Assertions.assertTrue(result.size() > 0); + Assertions.assertEquals(address, result.get(0)); + + + try { + instance.unregister(address); + } catch (Exception e) { + Assertions.fail(e.getMessage()); + } + + try { + TimeUnit.SECONDS.sleep(3); + } catch (InterruptedException ignore) { + } + + try { + result = instance.lookup("my_test_tx_group"); + } catch (Exception e) { + Assertions.fail(e.getMessage()); + } + + Assertions.assertEquals(0, result.size()); + + } + + + @AfterAll + public static void afterClass() { + System.setProperty("serverAddr", ""); + System.setProperty("addressWaitTime", "0"); + + + try { + registryMain.stopRegistry(); + } catch (Exception ignore) { + //ignore + } + } + +} diff --git a/discovery/seata-discovery-zk/pom.xml b/discovery/seata-discovery-zk/pom.xml new file mode 100644 index 0000000..87c13f7 --- /dev/null +++ b/discovery/seata-discovery-zk/pom.xml @@ -0,0 +1,44 @@ + + + + + io.seata + seata-discovery + ${revision} + + 4.0.0 + seata-discovery-zk + seata-discovery-zk ${project.version} + + + + io.seata + seata-discovery-core + ${project.parent.version} + + + com.101tec + zkclient + + + org.apache.curator + curator-test + test + + + diff --git a/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImpl.java b/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImpl.java new file mode 100644 index 0000000..d858c14 --- /dev/null +++ b/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImpl.java @@ -0,0 +1,312 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.zk; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NetUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.discovery.registry.RegistryService; +import org.I0Itec.zkclient.IZkChildListener; +import org.I0Itec.zkclient.IZkStateListener; +import org.I0Itec.zkclient.ZkClient; +import org.apache.zookeeper.Watcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.Constants.IP_PORT_SPLIT_CHAR; + +/** + * zookeeper path as /registry/zk/ + * + * @author crazier.huang + */ +public class ZookeeperRegisterServiceImpl implements RegistryService { + private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperRegisterServiceImpl.class); + + private static volatile ZookeeperRegisterServiceImpl instance; + private static volatile ZkClient zkClient; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String ZK_PATH_SPLIT_CHAR = "/"; + private static final String FILE_ROOT_REGISTRY = "registry"; + private static final String FILE_CONFIG_SPLIT_CHAR = "."; + private static final String REGISTRY_CLUSTER = "cluster"; + private static final String REGISTRY_TYPE = "zk"; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String AUTH_USERNAME = "username"; + private static final String AUTH_PASSWORD = "password"; + private static final String SESSION_TIME_OUT_KEY = "sessionTimeout"; + private static final String CONNECT_TIME_OUT_KEY = "connectTimeout"; + private static final int DEFAULT_SESSION_TIMEOUT = 6000; + private static final int DEFAULT_CONNECT_TIMEOUT = 2000; + private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE + + FILE_CONFIG_SPLIT_CHAR; + private static final String ROOT_PATH = ZK_PATH_SPLIT_CHAR + FILE_ROOT_REGISTRY + ZK_PATH_SPLIT_CHAR + REGISTRY_TYPE + + ZK_PATH_SPLIT_CHAR; + private static final String ROOT_PATH_WITHOUT_SUFFIX = ZK_PATH_SPLIT_CHAR + FILE_ROOT_REGISTRY + ZK_PATH_SPLIT_CHAR + + REGISTRY_TYPE; + private static final ConcurrentMap> CLUSTER_ADDRESS_MAP = new ConcurrentHashMap<>(); + private static final ConcurrentMap> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>(); + + private static final int REGISTERED_PATH_SET_SIZE = 1; + private static final Set REGISTERED_PATH_SET = Collections.synchronizedSet(new HashSet<>(REGISTERED_PATH_SET_SIZE)); + + private ZookeeperRegisterServiceImpl() { + } + + static ZookeeperRegisterServiceImpl getInstance() { + if (instance == null) { + synchronized (ZookeeperRegisterServiceImpl.class) { + if (instance == null) { + instance = new ZookeeperRegisterServiceImpl(); + } + } + } + return instance; + } + + @Override + public void register(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + + String path = getRegisterPathByPath(address); + doRegister(path); + } + + private boolean doRegister(String path) { + if (checkExists(path)) { + return false; + } + createParentIfNotPresent(path); + getClientInstance().createEphemeral(path, true); + REGISTERED_PATH_SET.add(path); + return true; + } + + private void createParentIfNotPresent(String path) { + int i = path.lastIndexOf('/'); + if (i > 0) { + String parent = path.substring(0, i); + if (!checkExists(parent)) { + getClientInstance().createPersistent(parent); + } + } + } + + private boolean checkExists(String path) { + return getClientInstance().exists(path); + } + + @Override + public void unregister(InetSocketAddress address) throws Exception { + NetUtil.validAddress(address); + + String path = getRegisterPathByPath(address); + getClientInstance().delete(path); + REGISTERED_PATH_SET.remove(path); + } + + @Override + public void subscribe(String cluster, IZkChildListener listener) throws Exception { + if (cluster == null) { + return; + } + + String path = ROOT_PATH + cluster; + if (!getClientInstance().exists(path)) { + getClientInstance().createPersistent(path); + } + getClientInstance().subscribeChildChanges(path, listener); + LISTENER_SERVICE_MAP.computeIfAbsent(cluster, key -> new CopyOnWriteArrayList<>()) + .add(listener); + } + + @Override + public void unsubscribe(String cluster, IZkChildListener listener) throws Exception { + if (cluster == null) { + return; + } + String path = ROOT_PATH + cluster; + if (getClientInstance().exists(path)) { + getClientInstance().unsubscribeChildChanges(path, listener); + + List subscribeList = LISTENER_SERVICE_MAP.get(cluster); + if (subscribeList != null) { + List newSubscribeList = subscribeList.stream() + .filter(eventListener -> !eventListener.equals(listener)) + .collect(Collectors.toList()); + LISTENER_SERVICE_MAP.put(cluster, newSubscribeList); + } + } + + } + + /** + * @param key the key + * @return + * @throws Exception + */ + @Override + public List lookup(String key) throws Exception { + String clusterName = getServiceGroup(key); + + if (clusterName == null) { + return null; + } + + return doLookup(clusterName); + } + + // visible for test. + List doLookup(String clusterName) throws Exception { + boolean exist = getClientInstance().exists(ROOT_PATH + clusterName); + if (!exist) { + return null; + } + + if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) { + List childClusterPath = getClientInstance().getChildren(ROOT_PATH + clusterName); + refreshClusterAddressMap(clusterName, childClusterPath); + subscribeCluster(clusterName); + } + + return CLUSTER_ADDRESS_MAP.get(clusterName); + } + + @Override + public void close() throws Exception { + getClientInstance().close(); + } + + private ZkClient getClientInstance() { + if (zkClient == null) { + synchronized (ZookeeperRegisterServiceImpl.class) { + if (zkClient == null) { + zkClient = buildZkClient(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY), + FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + SESSION_TIME_OUT_KEY, DEFAULT_SESSION_TIMEOUT), + FILE_CONFIG.getInt(FILE_CONFIG_KEY_PREFIX + CONNECT_TIME_OUT_KEY, DEFAULT_CONNECT_TIMEOUT), + FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_USERNAME), + FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + AUTH_PASSWORD)); + } + } + } + return zkClient; + } + + // visible for test. + ZkClient buildZkClient(String address, int sessionTimeout, int connectTimeout,String... authInfo) { + ZkClient zkClient = new ZkClient(address, sessionTimeout, connectTimeout); + if (authInfo != null && authInfo.length == 2) { + if (!StringUtils.isBlank(authInfo[0]) && !StringUtils.isBlank(authInfo[1])) { + StringBuilder auth = new StringBuilder(authInfo[0]).append(":").append(authInfo[1]); + zkClient.addAuthInfo("digest", auth.toString().getBytes()); + } + } + if (!zkClient.exists(ROOT_PATH_WITHOUT_SUFFIX)) { + zkClient.createPersistent(ROOT_PATH_WITHOUT_SUFFIX, true); + } + zkClient.subscribeStateChanges(new IZkStateListener() { + + @Override + public void handleStateChanged(Watcher.Event.KeeperState keeperState) throws Exception { + //ignore + } + + @Override + public void handleNewSession() throws Exception { + recover(); + } + + @Override + public void handleSessionEstablishmentError(Throwable throwable) throws Exception { + //ignore + } + }); + return zkClient; + } + + private void recover() throws Exception { + // recover Server + if (!REGISTERED_PATH_SET.isEmpty()) { + REGISTERED_PATH_SET.forEach(this::doRegister); + } + // recover client + if (!LISTENER_SERVICE_MAP.isEmpty()) { + Map> listenerMap = new HashMap<>(LISTENER_SERVICE_MAP); + LISTENER_SERVICE_MAP.clear(); + for (Map.Entry> listenerEntry : listenerMap.entrySet()) { + List iZkChildListeners = listenerEntry.getValue(); + if (CollectionUtils.isEmpty(iZkChildListeners)) { + continue; + } + for (IZkChildListener listener : iZkChildListeners) { + subscribe(listenerEntry.getKey(), listener); + } + } + } + } + + private void subscribeCluster(String cluster) throws Exception { + subscribe(cluster, (parentPath, currentChilds) -> { + String clusterName = parentPath.replace(ROOT_PATH, ""); + if (CollectionUtils.isEmpty(currentChilds) && CLUSTER_ADDRESS_MAP.get(clusterName) != null) { + CLUSTER_ADDRESS_MAP.remove(clusterName); + } else if (!CollectionUtils.isEmpty(currentChilds)) { + refreshClusterAddressMap(clusterName, currentChilds); + } + }); + } + + private void refreshClusterAddressMap(String clusterName, List instances) { + List newAddressList = new ArrayList<>(); + if (instances == null) { + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); + return; + } + for (String path : instances) { + try { + String[] ipAndPort = path.split(IP_PORT_SPLIT_CHAR); + newAddressList.add(new InetSocketAddress(ipAndPort[0], Integer.parseInt(ipAndPort[1]))); + } catch (Exception e) { + LOGGER.warn("The cluster instance info is error, instance info:{}", path); + } + } + CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); + } + + private String getClusterName() { + String clusterConfigName = String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_CLUSTER); + return FILE_CONFIG.getConfig(clusterConfigName); + } + + private String getRegisterPathByPath(InetSocketAddress address) { + return ROOT_PATH + getClusterName() + ZK_PATH_SPLIT_CHAR + NetUtil.toStringAddress(address); + } +} diff --git a/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegistryProvider.java b/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegistryProvider.java new file mode 100644 index 0000000..fdab679 --- /dev/null +++ b/discovery/seata-discovery-zk/src/main/java/io/seata/discovery/registry/zk/ZookeeperRegistryProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.zk; + +import io.seata.common.loader.LoadLevel; +import io.seata.discovery.registry.RegistryProvider; +import io.seata.discovery.registry.RegistryService; + +/** + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = "ZK", order = 1) +public class ZookeeperRegistryProvider implements RegistryProvider { + @Override + public RegistryService provide() { + return ZookeeperRegisterServiceImpl.getInstance(); + } +} diff --git a/discovery/seata-discovery-zk/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider b/discovery/seata-discovery-zk/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider new file mode 100644 index 0000000..62fd7b0 --- /dev/null +++ b/discovery/seata-discovery-zk/src/main/resources/META-INF/services/io.seata.discovery.registry.RegistryProvider @@ -0,0 +1 @@ +io.seata.discovery.registry.zk.ZookeeperRegistryProvider \ No newline at end of file diff --git a/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImplTest.java b/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImplTest.java new file mode 100644 index 0000000..52b0725 --- /dev/null +++ b/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegisterServiceImplTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.zk; + +import io.seata.common.util.NetUtil; +import org.I0Itec.zkclient.IZkChildListener; +import org.I0Itec.zkclient.ZkClient; +import org.apache.curator.test.TestingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * @author Geng Zhang + */ +public class ZookeeperRegisterServiceImplTest { + protected static TestingServer server = null; + + @BeforeAll + public static void adBeforeClass() throws Exception { + server = new TestingServer(2181, true); + server.start(); + } + + @AfterAll + public static void adAfterClass() throws Exception { + if (server != null) { + server.stop(); + } + } + + ZookeeperRegisterServiceImpl service = (ZookeeperRegisterServiceImpl) new ZookeeperRegistryProvider().provide(); + + @Test + public void getInstance() { + ZookeeperRegisterServiceImpl service1 = ZookeeperRegisterServiceImpl.getInstance(); + Assertions.assertEquals(service1, service); + } + + @Test + public void buildZkTest() { + ZkClient client = service.buildZkClient("127.0.0.1:2181", 5000, 5000); + Assertions.assertTrue(client.exists("/zookeeper")); + } + + @Test + public void testAll() throws Exception { + service.register(new InetSocketAddress(NetUtil.getLocalAddress(), 33333)); + + Assertions.assertNull(service.lookup("xxx")); + List lookup2 = service.doLookup("default"); + Assertions.assertEquals(1, lookup2.size()); + + final List data = new ArrayList<>(); + final CountDownLatch latch = new CountDownLatch(1); + IZkChildListener listener = (s, list) -> { + data.clear(); + data.addAll(list); + latch.countDown(); + }; + service.subscribe("default", listener); + final CountDownLatch latch2 = new CountDownLatch(1); + final List data2 = new ArrayList<>(); + IZkChildListener listener2 = (s, list) -> { + data2.clear(); + data2.addAll(list); + latch2.countDown(); + }; + service.subscribe("default", listener2); + + service.unregister(new InetSocketAddress(NetUtil.getLocalAddress(), 33333)); + latch2.await(1000, TimeUnit.MILLISECONDS); + Assertions.assertEquals(0, data2.size()); + + service.unsubscribe("default", listener); + service.unsubscribe("default", listener2); + } + +} diff --git a/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegistryProviderTest.java b/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegistryProviderTest.java new file mode 100644 index 0000000..9cd0b81 --- /dev/null +++ b/discovery/seata-discovery-zk/src/test/java/io/seata/discovery/registry/zk/ZookeeperRegistryProviderTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.discovery.registry.zk; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +public class ZookeeperRegistryProviderTest { + + @Test + public void provide() { + ZookeeperRegistryProvider provider = new ZookeeperRegistryProvider(); + Assertions.assertTrue(provider.provide() instanceof ZookeeperRegisterServiceImpl); + } + +} diff --git a/distribution/Dockerfile b/distribution/Dockerfile new file mode 100644 index 0000000..6186bd0 --- /dev/null +++ b/distribution/Dockerfile @@ -0,0 +1,18 @@ +# https://hub.docker.com/orgs/seataio +FROM openjdk:8u232-jre-stretch + +# set label +LABEL maintainer="Seata " + +WORKDIR /$BASE_DIR + +# ADD FORM distribution +ADD bin/ /seata-server/bin +ADD lib/ /seata-server/lib +ADD conf/ /seata-server/conf +ADD LICENSE-BIN /seata-server/LICENSE + +# set extra environment +ENV EXTRA_JVM_ARGUMENTS="-Djava.security.egd=file:/dev/./urandom -server -Xss512k -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 -Xloggc:/var/log/seata_gc.log -verbose:gc -Dio.netty.leakDetectionLevel=advanced" + +CMD ["sh","/seata-server/bin/seata-server.sh"] diff --git a/distribution/LICENSE-BIN b/distribution/LICENSE-BIN new file mode 100644 index 0000000..7f77f44 --- /dev/null +++ b/distribution/LICENSE-BIN @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (properties) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/distribution/pom.xml b/distribution/pom.xml new file mode 100644 index 0000000..3e3fe02 --- /dev/null +++ b/distribution/pom.xml @@ -0,0 +1,116 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-distribution + pom + seata-distribution ${project.version} + + + + io.seata + seata-server + ${project.version} + + + + + release-seata + + + io.seata + seata-server + ${project.version} + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-mysql + package + + copy + + + + + mysql + mysql-connector-java + ${mysql.jdbc.version} + + + mysql + mysql-connector-java + ${mysql8.jdbc.version} + + + + ${basedir}/seata-server-${revision}/lib/jdbc + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.0.0 + + + release-seata.xml + + + + + make-assembly + install + + single + + + + + + seata + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + + diff --git a/distribution/release-seata.xml b/distribution/release-seata.xml new file mode 100644 index 0000000..9d64855 --- /dev/null +++ b/distribution/release-seata.xml @@ -0,0 +1,61 @@ + + + + server-${project.version} + true + + dir + tar.gz + zip + + + + + seata-server-${project.version}/plugins/** + seata-server-${project.version}/conf/** + seata-server-${project.version}/lib/** + seata-server-${project.version}/logs/ + + + seata-server-${project.version}/lib/mysql-connector-java-*.jar + + + + + + seata-server-${project.version}/bin/* + + 0755 + + + + + + LICENSE-BIN + seata-server-${project.version}/LICENSE + + + + + + true + + io.seata:seata-server + + + + \ No newline at end of file diff --git a/integration/dubbo-alibaba/pom.xml b/integration/dubbo-alibaba/pom.xml new file mode 100644 index 0000000..d5a45a1 --- /dev/null +++ b/integration/dubbo-alibaba/pom.xml @@ -0,0 +1,43 @@ + + + + + + io.seata + seata-parent + ${revision} + ../../pom.xml + + 4.0.0 + + seata-dubbo-alibaba + seata-dubbo-alibaba ${project.version} + + + + ${project.groupId} + seata-tm + ${project.version} + + + com.alibaba + dubbo + + + diff --git a/integration/dubbo-alibaba/src/main/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilter.java b/integration/dubbo-alibaba/src/main/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilter.java new file mode 100644 index 0000000..bbba610 --- /dev/null +++ b/integration/dubbo-alibaba/src/main/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo.alibaba; + +import com.alibaba.dubbo.common.extension.Activate; +import com.alibaba.dubbo.rpc.Filter; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcContext; +import com.alibaba.dubbo.rpc.RpcException; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import io.seata.core.constants.DubboConstants; +import io.seata.core.model.BranchType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Transaction propagation filter. + * + * @author sharajava + */ +@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100) +public class AlibabaDubboTransactionPropagationFilter implements Filter { + + private static final Logger LOGGER = LoggerFactory.getLogger(AlibabaDubboTransactionPropagationFilter.class); + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + if (!DubboConstants.ALIBABADUBBO) { + return invoker.invoke(invocation); + } + String xid = RootContext.getXID(); + BranchType branchType = RootContext.getBranchType(); + + String rpcXid = getRpcXid(); + String rpcBranchType = RpcContext.getContext().getAttachment(RootContext.KEY_BRANCH_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid); + } + boolean bind = false; + if (xid != null) { + RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name()); + } else { + if (rpcXid != null) { + RootContext.bind(rpcXid); + if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) { + RootContext.bindBranchType(BranchType.TCC); + } + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind xid [{}] branchType [{}] to RootContext", rpcXid, rpcBranchType); + } + } + } + try { + return invoker.invoke(invocation); + } finally { + if (bind) { + BranchType previousBranchType = RootContext.getBranchType(); + String unbindXid = RootContext.unbind(); + if (BranchType.TCC == previousBranchType) { + RootContext.unbindBranchType(); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind xid [{}] branchType [{}] from RootContext", unbindXid, previousBranchType); + } + if (!rpcXid.equalsIgnoreCase(unbindXid)) { + LOGGER.warn("xid in change during RPC from {} to {},branchType from {} to {}", rpcXid, unbindXid, + rpcBranchType != null ? rpcBranchType : "AT", previousBranchType); + if (unbindXid != null) { + RootContext.bind(unbindXid); + LOGGER.warn("bind xid [{}] back to RootContext", unbindXid); + if (BranchType.TCC == previousBranchType) { + RootContext.bindBranchType(BranchType.TCC); + LOGGER.warn("bind branchType [{}] back to RootContext", previousBranchType); + } + } + } + } + } + } + + /** + * get rpc xid + * @return + */ + private String getRpcXid() { + String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/integration/dubbo-alibaba/src/main/resources/META-INF/services/com.alibaba.dubbo.rpc.Filter b/integration/dubbo-alibaba/src/main/resources/META-INF/services/com.alibaba.dubbo.rpc.Filter new file mode 100644 index 0000000..2d5cc35 --- /dev/null +++ b/integration/dubbo-alibaba/src/main/resources/META-INF/services/com.alibaba.dubbo.rpc.Filter @@ -0,0 +1 @@ +io.seata.integration.dubbo.alibaba.AlibabaDubboTransactionPropagationFilter \ No newline at end of file diff --git a/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilterTest.java b/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilterTest.java new file mode 100644 index 0000000..955a556 --- /dev/null +++ b/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/AlibabaDubboTransactionPropagationFilterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo.alibaba; + +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.RpcContext; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.integration.dubbo.alibaba.mock.MockInvoker; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wang.liang + */ +public class AlibabaDubboTransactionPropagationFilterTest { + + private static final String DEFAULT_XID = "1234567890"; + + @Test + public void testInvoke_And_RootContext() { + AlibabaDubboTransactionPropagationFilter filter = new AlibabaDubboTransactionPropagationFilter(); + + // SAGA + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.SAGA.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.AT); + }), null); + assertThat(RootContext.unbind()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + + // TCC + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.TCC); + }), null); + assertThat(RootContext.unbind()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + + // TCC + RootContext.bind(DEFAULT_XID); + RootContext.bindBranchType(BranchType.SAGA); + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.SAGA); + }), null); + assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.unbindBranchType()).isEqualTo(BranchType.SAGA); + } +} diff --git a/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/mock/MockInvoker.java b/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/mock/MockInvoker.java new file mode 100644 index 0000000..1cb8b93 --- /dev/null +++ b/integration/dubbo-alibaba/src/test/java/io/seata/integration/dubbo/alibaba/mock/MockInvoker.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo.alibaba.mock; + +import com.alibaba.dubbo.common.URL; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcException; + +/** + * @author wang.liang + */ +public class MockInvoker implements Invoker { + + private Runnable runnable; + + public MockInvoker() { + } + + public MockInvoker(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public Class getInterface() { + return null; + } + + @Override + public Result invoke(Invocation invocation) throws RpcException { + if (runnable != null) { + runnable.run(); + } + return null; + } + + @Override + public URL getUrl() { + return null; + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public void destroy() { + + } +} diff --git a/integration/dubbo/pom.xml b/integration/dubbo/pom.xml new file mode 100644 index 0000000..e07efc2 --- /dev/null +++ b/integration/dubbo/pom.xml @@ -0,0 +1,44 @@ + + + + + io.seata + seata-parent + ${revision} + ../../pom.xml + + 4.0.0 + seata-dubbo + jar + seata-dubbo ${project.version} + + + + ${project.groupId} + seata-tm + ${project.version} + + + org.apache.dubbo + dubbo + + + + + diff --git a/integration/dubbo/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java b/integration/dubbo/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java new file mode 100644 index 0000000..8d4f64c --- /dev/null +++ b/integration/dubbo/src/main/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilter.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo; + +import io.seata.common.util.StringUtils; +import io.seata.core.constants.DubboConstants; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Transaction propagation filter. + * + * @author sharajava + */ +@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100) +public class ApacheDubboTransactionPropagationFilter implements Filter { + + private static final Logger LOGGER = LoggerFactory.getLogger(ApacheDubboTransactionPropagationFilter.class); + + @Override + public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + String xid = RootContext.getXID(); + BranchType branchType = RootContext.getBranchType(); + + String rpcXid = getRpcXid(); + String rpcBranchType = RpcContext.getContext().getAttachment(RootContext.KEY_BRANCH_TYPE); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[{}] xid in RpcContext[{}]", xid, rpcXid); + } + boolean bind = false; + if (xid != null) { + RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name()); + } else { + if (rpcXid != null) { + RootContext.bind(rpcXid); + if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) { + RootContext.bindBranchType(BranchType.TCC); + } + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind xid [{}] branchType [{}] to RootContext", rpcXid, rpcBranchType); + } + } + } + try { + return invoker.invoke(invocation); + } finally { + if (bind) { + BranchType previousBranchType = RootContext.getBranchType(); + String unbindXid = RootContext.unbind(); + if (BranchType.TCC == previousBranchType) { + RootContext.unbindBranchType(); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind xid [{}] branchType [{}] from RootContext", unbindXid, previousBranchType); + } + if (!rpcXid.equalsIgnoreCase(unbindXid)) { + LOGGER.warn("xid in change during RPC from {} to {},branchType from {} to {}", rpcXid, unbindXid, + rpcBranchType != null ? rpcBranchType : "AT", previousBranchType); + if (unbindXid != null) { + RootContext.bind(unbindXid); + LOGGER.warn("bind xid [{}] back to RootContext", unbindXid); + if (BranchType.TCC == previousBranchType) { + RootContext.bindBranchType(BranchType.TCC); + LOGGER.warn("bind branchType [{}] back to RootContext", previousBranchType); + } + } + } + } + } + } + + /** + * get rpc xid + * @return + */ + private String getRpcXid() { + String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/integration/dubbo/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter b/integration/dubbo/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter new file mode 100644 index 0000000..2889307 --- /dev/null +++ b/integration/dubbo/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter @@ -0,0 +1 @@ +io.seata.integration.dubbo.ApacheDubboTransactionPropagationFilter \ No newline at end of file diff --git a/integration/dubbo/src/test/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilterTest.java b/integration/dubbo/src/test/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilterTest.java new file mode 100644 index 0000000..c89f714 --- /dev/null +++ b/integration/dubbo/src/test/java/io/seata/integration/dubbo/ApacheDubboTransactionPropagationFilterTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo; + +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.integration.dubbo.mock.MockInvoker; +import org.apache.dubbo.rpc.RpcContext; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author wang.liang + */ +public class ApacheDubboTransactionPropagationFilterTest { + + private static final String DEFAULT_XID = "1234567890"; + + @Test + public void testInvoke_And_RootContext() { + ApacheDubboTransactionPropagationFilter filter = new ApacheDubboTransactionPropagationFilter(); + + // SAGA + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.SAGA.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.AT); + }), null); + assertThat(RootContext.unbind()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + + // TCC + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.TCC); + }), null); + assertThat(RootContext.unbind()).isNull(); + assertThat(RootContext.unbindBranchType()).isNull(); + + // TCC + RootContext.bind(DEFAULT_XID); + RootContext.bindBranchType(BranchType.SAGA); + RpcContext.getContext().setAttachment(RootContext.KEY_XID, DEFAULT_XID); + RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, BranchType.TCC.name()); + filter.invoke(new MockInvoker(() -> { + assertThat(RootContext.getXID()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.getBranchType()).isEqualTo(BranchType.SAGA); + }), null); + assertThat(RootContext.unbind()).isEqualTo(DEFAULT_XID); + assertThat(RootContext.unbindBranchType()).isEqualTo(BranchType.SAGA); + } +} diff --git a/integration/dubbo/src/test/java/io/seata/integration/dubbo/mock/MockInvoker.java b/integration/dubbo/src/test/java/io/seata/integration/dubbo/mock/MockInvoker.java new file mode 100644 index 0000000..4e98d1f --- /dev/null +++ b/integration/dubbo/src/test/java/io/seata/integration/dubbo/mock/MockInvoker.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.dubbo.mock; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; + +/** + * @author wang.liang + */ +public class MockInvoker implements Invoker { + + private Runnable runnable; + + public MockInvoker() { + } + + public MockInvoker(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public Class getInterface() { + return null; + } + + @Override + public Result invoke(Invocation invocation) throws RpcException { + if (runnable != null) { + runnable.run(); + } + return null; + } + + @Override + public URL getUrl() { + return null; + } + + @Override + public boolean isAvailable() { + return false; + } + + @Override + public void destroy() { + + } +} diff --git a/integration/grpc/pom.xml b/integration/grpc/pom.xml new file mode 100644 index 0000000..7ea7600 --- /dev/null +++ b/integration/grpc/pom.xml @@ -0,0 +1,85 @@ + + + + + seata-parent + io.seata + ${revision} + ../../pom.xml + + 4.0.0 + seata-grpc + jar + seata-grpc ${project.version} + + + + + ${project.groupId} + seata-tm + ${project.version} + + + io.grpc + grpc-netty + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + + io.grpc + grpc-testing + test + true + + + javax.annotation + javax.annotation-api + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected.classifier} + + + + + test-compile + test-compile-custom + + + + + + + + \ No newline at end of file diff --git a/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/GrpcHeaderKey.java b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/GrpcHeaderKey.java new file mode 100644 index 0000000..0bbf4d5 --- /dev/null +++ b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/GrpcHeaderKey.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.grpc.interceptor; + +import io.grpc.Metadata; +import io.seata.core.context.RootContext; + +/** + * @author eddyxu1213@126.com + */ +public interface GrpcHeaderKey { + + Metadata.Key HEADER_KEY = Metadata.Key.of(RootContext.KEY_XID, Metadata.ASCII_STRING_MARSHALLER); + + Metadata.Key HEADER_KEY_LOWERCASE = Metadata.Key.of(RootContext.KEY_XID.toLowerCase(), Metadata.ASCII_STRING_MARSHALLER); +} diff --git a/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/client/ClientTransactionInterceptor.java b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/client/ClientTransactionInterceptor.java new file mode 100644 index 0000000..2cdbf82 --- /dev/null +++ b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/client/ClientTransactionInterceptor.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.grpc.interceptor.client; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.seata.core.context.RootContext; +import io.seata.integration.grpc.interceptor.GrpcHeaderKey; + +/** + * @author eddyxu1213@126.com + */ +public class ClientTransactionInterceptor implements ClientInterceptor { + + @Override + public ClientCall interceptCall( + MethodDescriptor method, + CallOptions callOptions, + Channel next) { + + String xid = RootContext.getXID(); + return new ForwardingClientCall.SimpleForwardingClientCall(next.newCall(method, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + if (xid != null) { + headers.put(GrpcHeaderKey.HEADER_KEY, xid); + } + super.start(new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) { + @Override + public void onHeaders(Metadata headers) { + super.onHeaders(headers); + } + }, headers); + } + }; + } +} diff --git a/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerListenerProxy.java b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerListenerProxy.java new file mode 100644 index 0000000..03d8384 --- /dev/null +++ b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerListenerProxy.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.grpc.interceptor.server; + +import io.grpc.ServerCall; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; + +import java.util.Objects; + +/** + * @author eddyxu1213@126.com + */ +public class ServerListenerProxy extends ServerCall.Listener { + + private ServerCall.Listener target; + private String xid; + + public ServerListenerProxy(String xid, ServerCall.Listener target) { + super(); + Objects.requireNonNull(target); + this.target = target; + this.xid = xid; + } + + @Override + public void onMessage(ReqT message) { + target.onMessage(message); + } + + @Override + public void onHalfClose() { + if (StringUtils.isNotBlank(xid)) { + RootContext.bind(xid); + } + target.onHalfClose(); + } + + @Override + public void onCancel() { + if (StringUtils.isNotBlank(xid) && RootContext.inGlobalTransaction()) { + RootContext.unbind(); + } + target.onCancel(); + } + + @Override + public void onComplete() { + if (StringUtils.isNotBlank(xid) && RootContext.inGlobalTransaction()) { + RootContext.unbind(); + } + target.onComplete(); + } + + @Override + public void onReady() { + target.onReady(); + } +} diff --git a/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerTransactionInterceptor.java b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerTransactionInterceptor.java new file mode 100644 index 0000000..3c78658 --- /dev/null +++ b/integration/grpc/src/main/java/io/seata/integration/grpc/interceptor/server/ServerTransactionInterceptor.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.grpc.interceptor.server; + +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.seata.integration.grpc.interceptor.GrpcHeaderKey; + +/** + * @author eddyxu1213@126.com + */ +public class ServerTransactionInterceptor implements ServerInterceptor { + + @Override + public ServerCall.Listener interceptCall( + ServerCall serverCall, + Metadata metadata, + ServerCallHandler serverCallHandler) { + String xid = getRpcXid(metadata); + return new ServerListenerProxy<>(xid, serverCallHandler.startCall(serverCall, metadata)); + } + + /** + * get rpc xid + * @param metadata + * @return + */ + private String getRpcXid(Metadata metadata) { + String rpcXid = metadata.get(GrpcHeaderKey.HEADER_KEY); + if (rpcXid == null) { + rpcXid = metadata.get(GrpcHeaderKey.HEADER_KEY_LOWERCASE); + } + return rpcXid; + } + +} diff --git a/integration/grpc/src/test/java/io/seata/integration/grpc/interceptor/GrpcTest.java b/integration/grpc/src/test/java/io/seata/integration/grpc/interceptor/GrpcTest.java new file mode 100644 index 0000000..4a3fb7f --- /dev/null +++ b/integration/grpc/src/test/java/io/seata/integration/grpc/interceptor/GrpcTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.grpc.interceptor; + +import com.google.common.util.concurrent.ListenableFuture; +import io.grpc.ClientInterceptors; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.ServerInterceptor; +import io.grpc.ServerInterceptors; +import io.grpc.inprocess.InProcessChannelBuilder; +import io.grpc.inprocess.InProcessServerBuilder; +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcCleanupRule; +import io.seata.core.context.RootContext; +import io.seata.integration.grpc.interceptor.client.ClientTransactionInterceptor; +import io.seata.integration.grpc.interceptor.proto.ContextRpcGrpc; +import io.seata.integration.grpc.interceptor.proto.Request; +import io.seata.integration.grpc.interceptor.proto.Response; +import io.seata.integration.grpc.interceptor.server.ServerTransactionInterceptor; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalAnswers.delegatesTo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +/** + * @author eddyxu1213@126.com + */ +public class GrpcTest { + + @Rule + public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule(); + private final ServerInterceptor mockServerInterceptor = mock(ServerInterceptor.class, delegatesTo(new ServerTransactionInterceptor())); + + + @Test + public void clientHeaderDeliveredToServer() throws Exception { + + String serverName = InProcessServerBuilder.generateName(); + CountDownLatch countDownLatch = new CountDownLatch(1); + String[] context = {null}; + + //executor + Executor executorService = new ThreadPoolExecutor(2, 2, 1, TimeUnit.HOURS, new LinkedBlockingQueue<>(), new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "contextText-" + System.currentTimeMillis()); + } + }); + + //server + grpcCleanup.register(InProcessServerBuilder.forName(serverName).executor(executorService) + .addService(ServerInterceptors.intercept(new ContextRpcGrpc.ContextRpcImplBase() { + @Override + public void contextRpc(Request request, StreamObserver responseObserver) { + context[0] = RootContext.getXID(); + countDownLatch.countDown(); + responseObserver.onNext(Response.newBuilder().setGreet("hello! " + request.getName()).build()); + responseObserver.onCompleted(); + } + }, mockServerInterceptor)) + .build().start()); + + //client + ManagedChannel channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).executor(executorService).build()); + ContextRpcGrpc.ContextRpcFutureStub stub = ContextRpcGrpc.newFutureStub( + ClientInterceptors.intercept(channel, new ClientTransactionInterceptor())); + RootContext.bind("test_context"); + ListenableFuture future = stub.contextRpc(Request.newBuilder().setName("seata").build()); + assertEquals("hello! seata", future.get().getGreet()); + + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(Metadata.class); + verify(mockServerInterceptor).interceptCall(ArgumentMatchers.any(), metadataCaptor.capture(), ArgumentMatchers.any()); + assertEquals("test_context", metadataCaptor.getValue().get(GrpcHeaderKey.HEADER_KEY)); + + countDownLatch.await(); + assertEquals("test_context", context[0]); + } +} diff --git a/integration/grpc/src/test/proto/contextTest.proto b/integration/grpc/src/test/proto/contextTest.proto new file mode 100644 index 0000000..8e2f0d8 --- /dev/null +++ b/integration/grpc/src/test/proto/contextTest.proto @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.seata.integration.grpc.interceptor.proto"; +option java_outer_classname = "ContextRpcTest"; + +service ContextRpc { + rpc ContextRpc (Request) returns (Response) { + } +} + +message Request { + string name = 1; + +} + +message Response { + string greet = 1; +} diff --git a/integration/http/pom.xml b/integration/http/pom.xml new file mode 100644 index 0000000..1b539ff --- /dev/null +++ b/integration/http/pom.xml @@ -0,0 +1,61 @@ + + + + + io.seata + seata-parent + ${revision} + ../../pom.xml + + 4.0.0 + seata-http + jar + seata-http ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + + org.springframework + spring-webmvc + + + javax.servlet + servlet-api + 2.5 + provided + + + org.apache.httpcomponents + httpclient + + + + com.alibaba + fastjson + + + + + + diff --git a/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java b/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java new file mode 100644 index 0000000..dbea564 --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/AbstractHttpExecutor.java @@ -0,0 +1,158 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.serializer.SerializerFeature; +import io.seata.common.util.CollectionUtils; +import io.seata.core.context.RootContext; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.Args; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract http executor. + * + * @author wangxb + */ +public abstract class AbstractHttpExecutor implements HttpExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpExecutor.class); + + @Override + public K executePost(String host, String path, T paramObject, Class returnType) throws IOException { + + Args.notNull(returnType, "returnType"); + Args.notNull(host, "host"); + Args.notNull(path, "path"); + + CloseableHttpClient httpClient = initHttpClientInstance(paramObject); + HttpPost httpPost = new HttpPost(host + path); + StringEntity entity = null; + if (paramObject != null) { + String content; + if (paramObject instanceof String) { + String sParam = (String) paramObject; + JSONObject jsonObject = null; + try { + jsonObject = JSON.parseObject(sParam); + content = jsonObject.toJSONString(); + } catch (JSONException e) { + //Interface provider process parse exception + if (LOGGER.isWarnEnabled()) { + LOGGER.warn(e.getMessage()); + } + content = sParam; + } + + } else { + content = JSON.toJSONString(paramObject); + } + entity = new StringEntity(content, ContentType.APPLICATION_JSON); + } + + entity = buildEntity(entity, paramObject); + if (entity != null) { + httpPost.setEntity(entity); + } + Map headers = new HashMap<>(); + + buildPostHeaders(headers, paramObject); + return wrapHttpExecute(returnType, httpClient, httpPost, headers); + } + + @Override + public K executeGet(String host, String path, Map paramObject, Class returnType) throws IOException { + + Args.notNull(returnType, "returnType"); + Args.notNull(host, "host"); + Args.notNull(path, "path"); + + CloseableHttpClient httpClient = initHttpClientInstance(paramObject); + + HttpGet httpGet = new HttpGet(initGetUrl(host, path, paramObject)); + Map headers = new HashMap<>(); + + buildGetHeaders(headers, paramObject); + return wrapHttpExecute(returnType, httpClient, httpGet, headers); + } + + private CloseableHttpClient initHttpClientInstance(T paramObject) { + CloseableHttpClient httpClient = HttpClients.createDefault(); + buildClientEntity(httpClient, paramObject); + return httpClient; + } + + protected abstract void buildClientEntity(CloseableHttpClient httpClient, T paramObject); + + private K wrapHttpExecute(Class returnType, CloseableHttpClient httpClient, HttpUriRequest httpUriRequest, + Map headers) throws IOException { + CloseableHttpResponse response; + String xid = RootContext.getXID(); + if (xid != null) { + headers.put(RootContext.KEY_XID, xid); + } + if (!headers.isEmpty()) { + headers.forEach(httpUriRequest::addHeader); + } + response = httpClient.execute(httpUriRequest); + int statusCode = response.getStatusLine().getStatusCode(); + /** 2xx is success. */ + if (statusCode < HttpStatus.SC_OK || statusCode > HttpStatus.SC_MULTI_STATUS) { + throw new RuntimeException("Failed to invoke the http method " + + httpUriRequest.getURI() + " in the service " + + ". return status by: " + response.getStatusLine().getStatusCode()); + } + + return convertResult(response, returnType); + } + + protected abstract void buildGetHeaders(Map headers, T paramObject); + + protected abstract String initGetUrl(String host, String path, Map paramObject); + + + protected abstract void buildPostHeaders(Map headers, T t); + + protected abstract StringEntity buildEntity(StringEntity entity, T t); + + protected abstract K convertResult(HttpResponse response, Class clazz); + + + public static Map convertParamOfBean(Object sourceParam) { + return CollectionUtils.toStringMap(JSON.parseObject(JSON.toJSONString(sourceParam, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteMapNullValue), Map.class)); + } + + public static Map convertParamOfJsonString(String jsonStr, Class returnType) { + return convertParamOfBean(JSON.parseObject(jsonStr, returnType)); + } +} diff --git a/integration/http/src/main/java/io/seata/integration/http/DefaultHttpExecutor.java b/integration/http/src/main/java/io/seata/integration/http/DefaultHttpExecutor.java new file mode 100644 index 0000000..1dd3af2 --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/DefaultHttpExecutor.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; + +/** + * Default http executor. + * + * @author wangxb + */ +public class DefaultHttpExecutor extends AbstractHttpExecutor { + + private static DefaultHttpExecutor instance = new DefaultHttpExecutor(); + + private DefaultHttpExecutor() { + } + + public static DefaultHttpExecutor getInstance() { + return instance; + } + + @Override + public void buildClientEntity(CloseableHttpClient httpClient, T paramObject) { + + } + + @Override + public void buildGetHeaders(Map headers, T paramObject) { + + } + + + @Override + public String initGetUrl(String host, String path, Map querys) { + + if (querys.isEmpty()) { + return host + path; + } + StringBuilder sbUrl = new StringBuilder(); + sbUrl.append(host); + if (!StringUtils.isBlank(path)) { + sbUrl.append(path); + } + + StringBuilder sbQuery = new StringBuilder(); + Iterator queryKeys = querys.entrySet().iterator(); + + while (queryKeys.hasNext()) { + Map.Entry query = (Map.Entry) queryKeys.next(); + if (0 < sbQuery.length()) { + sbQuery.append("&"); + } + + if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { + sbQuery.append(query.getValue()); + } + + if (!StringUtils.isBlank(query.getKey())) { + sbQuery.append(query.getKey()); + if (!StringUtils.isBlank(query.getValue())) { + sbQuery.append("="); + try { + sbQuery.append(URLEncoder.encode(query.getValue(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.getMessage()); + } + } + } + } + + if (sbQuery.length() > 0) { + sbUrl.append("?").append(sbQuery); + } + + return sbUrl.toString(); + + } + + @Override + public void buildPostHeaders(Map headers, T t) { + + } + + @Override + public StringEntity buildEntity(StringEntity entity, T t) { + return entity; + } + + @Override + public K convertResult(HttpResponse response, Class clazz) { + + + if (clazz == HttpResponse.class) { + return (K) response; + } + return null; + } + +} diff --git a/integration/http/src/main/java/io/seata/integration/http/HttpExecutor.java b/integration/http/src/main/java/io/seata/integration/http/HttpExecutor.java new file mode 100644 index 0000000..50d52a7 --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/HttpExecutor.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import java.io.IOException; +import java.util.Map; + +/** + * Http executor. + * + * @author wangxb + */ +public interface HttpExecutor { + + /** + * Execute post k. + * + * @param the type parameter + * @param the type parameter + * @param host the host + * @param path the path + * @param paramObject the param object + * @param returnType the return type + * @return the k + * @throws IOException the io exception + */ + K executePost(String host, String path, T paramObject, Class returnType) throws IOException; + + /** + * get method only support param type of Map + * + * @param the type parameter + * @param host the host + * @param path the path + * @param paramObject the param object + * @param returnType the return type + * @return K k + * @throws IOException the io exception + */ + K executeGet(String host, String path, Map paramObject, Class returnType) throws IOException; + +} diff --git a/integration/http/src/main/java/io/seata/integration/http/HttpHandlerExceptionResolver.java b/integration/http/src/main/java/io/seata/integration/http/HttpHandlerExceptionResolver.java new file mode 100644 index 0000000..6b3d22c --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/HttpHandlerExceptionResolver.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.core.context.RootContext; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Http exception handle. + * + * @author wangxb + */ +public class HttpHandlerExceptionResolver extends AbstractHandlerExceptionResolver { + + + @Override + protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o, Exception e) { + + XidResource.cleanXid(request.getHeader(RootContext.KEY_XID)); + return null; + } +} diff --git a/integration/http/src/main/java/io/seata/integration/http/TransactionPropagationInterceptor.java b/integration/http/src/main/java/io/seata/integration/http/TransactionPropagationInterceptor.java new file mode 100644 index 0000000..5ec5042 --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/TransactionPropagationInterceptor.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + + +/** + * Springmvc Intercepter. + * + * @author wangxb + */ +public class TransactionPropagationInterceptor extends HandlerInterceptorAdapter { + + + private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationInterceptor.class); + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String xid = RootContext.getXID(); + String rpcXid = request.getHeader(RootContext.KEY_XID); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[{}] xid in HttpContext[{}]", xid, rpcXid); + } + if (xid == null && rpcXid != null) { + RootContext.bind(rpcXid); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind[{}] to RootContext", rpcXid); + } + } + + return true; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) { + if (RootContext.inGlobalTransaction()) { + XidResource.cleanXid(request.getHeader(RootContext.KEY_XID)); + } + } + +} diff --git a/integration/http/src/main/java/io/seata/integration/http/XidResource.java b/integration/http/src/main/java/io/seata/integration/http/XidResource.java new file mode 100644 index 0000000..cd8a54d --- /dev/null +++ b/integration/http/src/main/java/io/seata/integration/http/XidResource.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Xid handler. + * + * @author wangxb + */ +public class XidResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(XidResource.class); + + + public static void cleanXid(String rpcXid) { + String xid = RootContext.getXID(); + if (xid != null) { + String unbindXid = RootContext.unbind(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind[{}] from RootContext", unbindXid); + } + if (!StringUtils.equalsIgnoreCase(rpcXid, unbindXid)) { + LOGGER.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid); + if (unbindXid != null) { + RootContext.bind(unbindXid); + LOGGER.warn("bind [{}] back to RootContext", unbindXid); + } + } + } + } +} diff --git a/integration/http/src/test/java/io/seata/integration/http/HttpTest.java b/integration/http/src/test/java/io/seata/integration/http/HttpTest.java new file mode 100644 index 0000000..9b10202 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/HttpTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.seata.core.context.RootContext; +import org.apache.http.HttpResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.HashMap; +import java.util.Map; + +import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfBean; +import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfJsonString; + +/** + * @author wangxb + */ +class HttpTest { + + private static final String host = "http://127.0.0.1:8081"; + private static final String testException = "/testException"; + private static final String getPath = "/testGet"; + private static final String postPath = "/testPost"; + public static final String XID = "127.0.0.1:8081:87654321"; + private static final int PARAM_TYPE_MAP = 1; + private static final int PARAM_TYPE_BEAN = 2; + + @Test + void testGetProviderXID() { + RootContext.bind(XID); + providerStart(); + String result = consumerGetStart(PARAM_TYPE_MAP); + Assertions.assertEquals("Person{name='zhangsan', age=15}", result); + RootContext.unbind(); + } + + @Test + void testPostProviderXID() { + RootContext.bind(XID); + providerStart(); + String result = consumerPostStart(PARAM_TYPE_MAP); + Assertions.assertEquals("Person{name='zhangsan', age=15}", result); + RootContext.unbind(); + } + + @Test + void testGetExceptionRemoveXID() { + RootContext.bind(XID); + providerStart(); + String result = consumerGetExceptionStart(); + Assertions.assertEquals("Callee remove local xid success", result); + RootContext.unbind(); + } + + public void providerStart() { + new MockWebServer().start(8081); + } + + public static class Person { + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + } + + private String consumerPostStart(int param_type) { + DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance(); + String str = "{\n" + + " \"name\":\"zhangsan\",\n" + + " \"age\":15\n" + + "}"; + Person person = JSON.parseObject(str, Person.class); + + Map map = new HashMap<>(); + map.put("name", "zhangsan"); + map.put("age", 15); + + JSONObject json = new JSONObject(); + json.put("name", "zhangsan"); + json.put("age", 15); + + //The body parameter of post supports the above types (str,person,map,json) + try { + HttpResponse response; + + if (param_type == PARAM_TYPE_MAP) { + response = httpExecuter.executePost(host, postPath, map, HttpResponse.class); + } else if (param_type == PARAM_TYPE_BEAN) { + response = httpExecuter.executePost(host, postPath, person, HttpResponse.class); + } else { + response = httpExecuter.executePost(host, postPath, str, HttpResponse.class); + } + + return readStreamAsStr(response.getEntity().getContent()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String consumerGetStart(int param_type) { + DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance(); + Map params = new HashMap<>(); + params.put("name", "zhangsan"); + params.put("age", "15"); + + String str = "{\n" + + " \"name\":\"zhangsan\",\n" + + " \"age\":15\n" + + "}"; + Person person = JSON.parseObject(str, Person.class); + try { + //support all type of parameter types + HttpResponse response; + if (param_type == PARAM_TYPE_MAP) { + response = httpExecuter.executeGet(host, getPath, params, HttpResponse.class); + } else if (param_type == PARAM_TYPE_BEAN) { + response = httpExecuter.executeGet(host, getPath, convertParamOfBean(person), HttpResponse.class); + } else { + response = httpExecuter.executeGet(host, getPath, convertParamOfJsonString(str, Person.class), HttpResponse.class); + } + return readStreamAsStr(response.getEntity().getContent()); + + } catch (IOException e) { + /* if in Travis CI env, only mock method call */ + MockHttpExecuter mockHttpExecuter = new MockHttpExecuter(); + try { + return mockHttpExecuter.executeGet(host, getPath, params, String.class); + } catch (IOException ex) { + throw new RuntimeException(e); + } + } + } + + private String consumerGetExceptionStart() { + DefaultHttpExecutor httpExecuter = DefaultHttpExecutor.getInstance(); + Map params = new HashMap<>(); + params.put("name", "zhangsan"); + params.put("age", "15"); + HttpResponse response; + try { + response = httpExecuter.executeGet(host, testException, params, HttpResponse.class); + return readStreamAsStr(response.getEntity().getContent()); + } catch (IOException e) { + /* if in Travis CI inv, only mock method call */ + MockHttpExecuter mockHttpExecuter = new MockHttpExecuter(); + try { + return mockHttpExecuter.executeGet(host, testException, params, String.class); + } catch (IOException ex) { + throw new RuntimeException(e); + } + + } + + } + + public static String readStreamAsStr(InputStream is) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + WritableByteChannel dest = Channels.newChannel(bos); + ReadableByteChannel src = Channels.newChannel(is); + ByteBuffer bb = ByteBuffer.allocate(4096); + + while (src.read(bb) != -1) { + bb.flip(); + dest.write(bb); + bb.clear(); + } + + src.close(); + dest.close(); + return new String(bos.toByteArray(), "UTF-8"); + } + + @Test + void convertParamOfJsonStringTest() { + + String targetParam = "{name=zhangsan, age=15}"; + String str = "{\n" + + " \"name\":\"zhangsan\",\n" + + " \"age\":15\n" + + "}"; + Map map = convertParamOfJsonString(str, Person.class); + Assertions.assertEquals(map.toString(), targetParam); + Person person = JSON.parseObject(str, Person.class); + map = convertParamOfBean(person); + Assertions.assertEquals(map.toString(), targetParam); + + + } +} \ No newline at end of file diff --git a/integration/http/src/test/java/io/seata/integration/http/MockController.java b/integration/http/src/test/java/io/seata/integration/http/MockController.java new file mode 100644 index 0000000..6e2dffc --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockController.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.core.context.RootContext; +import org.junit.jupiter.api.Assertions; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author : wangxb + * @Description: Mock springmvc controller + */ +@Controller +public class MockController { + + + @RequestMapping("/testGet") + @ResponseBody + public String testGet(HttpTest.Person person) { + /* verify xid propagate by test case */ + Assertions.assertEquals(HttpTest.XID,RootContext.getXID()); + return person.toString(); + } + + + @ResponseBody + @PostMapping("/testPost") + public String testPost(@RequestBody HttpTest.Person person) { + /* verify xid propagate by test case */ + Assertions.assertEquals(HttpTest.XID,RootContext.getXID()); + return person.toString(); + } + + @RequestMapping("/testException") + @ResponseBody + public String testException(HttpTest.Person person) { + throw new RuntimeException(); + } + +} diff --git a/integration/http/src/test/java/io/seata/integration/http/MockHttpExecuter.java b/integration/http/src/test/java/io/seata/integration/http/MockHttpExecuter.java new file mode 100644 index 0000000..97fbfc8 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockHttpExecuter.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.core.context.RootContext; +import org.apache.http.HttpResponse; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.Args; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author : wangxb + */ +public class MockHttpExecuter extends AbstractHttpExecutor { + + DefaultHttpExecutor httpExecutor = DefaultHttpExecutor.getInstance(); + + @Override + public K executeGet(String host, String path, Map paramObject, Class returnType) throws IOException { + Args.notNull(host, "host"); + Args.notNull(path, "path"); + + String getUrl = initGetUrl(host, path, paramObject); + Map headers = new HashMap<>(); + + MockRequest mockRequest = new MockRequest(getUrl, headers, null, path, "get"); + MockResponse mockResponse = new MockResponse(null); + String xid = RootContext.getXID(); + if (xid != null) { + headers.put(RootContext.KEY_XID, xid); + } + MockWebServer webServer = new MockWebServer(); + webServer.initServletMapping(); + return (K) webServer.dispatch(mockRequest, mockResponse); + } + + @Override + protected void buildClientEntity(CloseableHttpClient httpClient, T paramObject) { + + } + + @Override + protected void buildGetHeaders(Map headers, T paramObject) { + + } + + @Override + protected String initGetUrl(String host, String path, Map paramObject) { + return httpExecutor.initGetUrl(host, path, paramObject); + } + + @Override + protected void buildPostHeaders(Map headers, T t) { + + } + + @Override + protected StringEntity buildEntity(StringEntity entity, T t) { + return null; + } + + @Override + protected K convertResult(HttpResponse response, Class clazz) { + return null; + } + + +} diff --git a/integration/http/src/test/java/io/seata/integration/http/MockHttpServletRequest.java b/integration/http/src/test/java/io/seata/integration/http/MockHttpServletRequest.java new file mode 100644 index 0000000..44c7351 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockHttpServletRequest.java @@ -0,0 +1,315 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.core.context.RootContext; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletInputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.BufferedReader; +import java.security.Principal; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Map; + +/** + * @author : wangxb + */ +public class MockHttpServletRequest implements HttpServletRequest { + + private MockRequest myRequest; + + public MockHttpServletRequest(MockRequest myRequest) { + this.myRequest = myRequest; + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public Cookie[] getCookies() { + return new Cookie[0]; + } + + @Override + public long getDateHeader(String name) { + return 0; + } + + @Override + public String getHeader(String name) { + if (RootContext.KEY_XID.equals(name)) + return myRequest.getHeader().get(RootContext.KEY_XID); + else { + return null; + } + } + + @Override + public Enumeration getHeaders(String name) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public int getIntHeader(String name) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public boolean isUserInRole(String role) { + return false; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession(boolean create) { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public void setCharacterEncoding(String env) { + + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() { + return null; + } + + @Override + public String getParameter(String name) { + return null; + } + + @Override + public Enumeration getParameterNames() { + return null; + } + + @Override + public String[] getParameterValues(String name) { + return new String[0]; + } + + @Override + public Map getParameterMap() { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getScheme() { + return null; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 0; + } + + @Override + public BufferedReader getReader() { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public void setAttribute(String name, Object o) { + + } + + @Override + public void removeAttribute(String name) { + + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration getLocales() { + return null; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + return null; + } + + @Override + public String getRealPath(String path) { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } +} diff --git a/integration/http/src/test/java/io/seata/integration/http/MockRequest.java b/integration/http/src/test/java/io/seata/integration/http/MockRequest.java new file mode 100644 index 0000000..59430e2 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockRequest.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import io.seata.core.context.RootContext; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * @author : wangxb + */ +public class MockRequest { + private String url; + private Map header = new HashMap<>(); + private String body; + private String path; + private String method = "get"; + + public String getBody() { + return body; + } + + public Map getHeader() { + return header; + } + + public String getMethod() { + return method; + } + + public String getPath() { + return path; + } + + public MockRequest(String url, Map header, String body, String path,String method) { + + this.url = url; + this.header = header; + this.body = body; + this.path = path; + this.method = method; + } + + public MockRequest(InputStream inputStream) throws IOException { + String httpRequest = ""; + byte[] httpRequestBytes = new byte[2048]; + int length = 0; + if ((length = inputStream.read(httpRequestBytes)) > 0) { + httpRequest = new String(httpRequestBytes, 0, length); + } + + String httpHead = httpRequest.split("\n")[0]; + url = httpHead.split("\\s")[1]; + String xid = httpRequest.split("\\n")[1]; + if (xid.startsWith(RootContext.KEY_XID)) { + xid = xid.split(RootContext.KEY_XID + ":")[1].trim(); + } + header.put(RootContext.KEY_XID, xid); + + path = url.split("\\?")[0]; + if (httpRequest.startsWith("POST")) { + body = httpRequest.split("\\n")[9]; + method = "post"; + } + } + + public String getUrl() { + return url; + } + + +} diff --git a/integration/http/src/test/java/io/seata/integration/http/MockResponse.java b/integration/http/src/test/java/io/seata/integration/http/MockResponse.java new file mode 100644 index 0000000..eb40905 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockResponse.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author : wangxb + */ +public class MockResponse { + + private OutputStream outputStream; + + public MockResponse(OutputStream outputStream) { + this.outputStream = outputStream; + } + + public String write(String content) throws IOException { + StringBuilder httpResponse = new StringBuilder(); + httpResponse.append("HTTP/1.1 200 OK\n") + .append("Content-Type:application/json\n") + .append("\r\n") + .append(content); + if (outputStream == null) { + //local call + return content; + } + else { + outputStream.write(httpResponse.toString().getBytes()); + outputStream.close(); + return "success"; + } + } +} diff --git a/integration/http/src/test/java/io/seata/integration/http/MockWebServer.java b/integration/http/src/test/java/io/seata/integration/http/MockWebServer.java new file mode 100644 index 0000000..1d1c16a --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/MockWebServer.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import com.alibaba.fastjson.JSONObject; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Map; + +import static io.seata.integration.http.AbstractHttpExecutor.convertParamOfJsonString; + +/** + * @author : wangxb + */ +public class MockWebServer { + + private Map urlServletMap = new HashMap<>(); + + + public void start(int port) { + initServletMapping(); + new Thread(() -> { + ServerSocket serverSocket = null; + try { + serverSocket = new ServerSocket(port); + Socket socket = serverSocket.accept(); + InputStream inputStream = socket.getInputStream(); + OutputStream outputStream = socket.getOutputStream(); + + MockRequest myRequest = new MockRequest(inputStream); + MockResponse myResponse = new MockResponse(outputStream); + + dispatch(myRequest, myResponse); + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }).start(); + + } + + public void initServletMapping() { + for (ServletMapping servletMapping : ServletMapping.servletMappingList) { + urlServletMap.put(servletMapping.getPath(), servletMapping.getClazz() + "_" + servletMapping.getMethod()); + } + } + + public String dispatch(MockRequest myRequest, MockResponse mockResponse) { + String clazz = urlServletMap.get(myRequest.getPath()).split("_")[0]; + String methodName = urlServletMap.get(myRequest.getPath()).split("_")[1]; + HttpServletRequest request = new MockHttpServletRequest(myRequest); + try { + Class myServletClass = (Class) Class.forName(clazz); + MockController myServlet = myServletClass.newInstance(); + HttpTest.Person person = boxing(myRequest); + Method method = myServletClass.getDeclaredMethod(methodName, HttpTest.Person.class); + + /* mock request intercepter */ + TransactionPropagationInterceptor intercepter = new TransactionPropagationInterceptor(); + + intercepter.preHandle(request, null, null); + Object result = method.invoke(myServlet, person); + + return mockResponse.write(result.toString()); + } catch (Exception e) { + HttpHandlerExceptionResolver resolver = new HttpHandlerExceptionResolver(); + resolver.doResolveException(request, null, null, e); + if (RootContext.getXID() == null) { + try { + return mockResponse.write("Callee remove local xid success"); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + throw new RuntimeException(e); + } + } + + private HttpTest.Person boxing(MockRequest myRequest) { + Map params = null; + if ("get".equals(myRequest.getMethod())) + params = getUrlParams(myRequest.getUrl()); + else if ("post".equals(myRequest.getMethod())) { + params = getBodyParams(myRequest.getBody()); + } + return JSONObject.parseObject(JSONObject.toJSONString(params), HttpTest.Person.class); + } + + private Map getBodyParams(String body) { + Map map = convertParamOfJsonString(body, HttpTest.Person.class); + return map; + } + + + public static Map getUrlParams(String param) { + Map map = new HashMap(0); + if (StringUtils.isBlank(param)) { + return map; + } + String[] urlPath = param.split("\\?"); + if (urlPath.length < 2) { + return map; + } + + String[] params = urlPath[1].split("&"); + for (int i = 0; i < params.length; i++) { + String[] p = params[i].split("="); + if (p.length == 2) { + map.put(p[0], p[1]); + } + } + return map; + + } +} \ No newline at end of file diff --git a/integration/http/src/test/java/io/seata/integration/http/ServletMapping.java b/integration/http/src/test/java/io/seata/integration/http/ServletMapping.java new file mode 100644 index 0000000..f20b6f6 --- /dev/null +++ b/integration/http/src/test/java/io/seata/integration/http/ServletMapping.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.http; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author : wangxb + */ +public class ServletMapping { + + public static List servletMappingList = new ArrayList<>(); + + static { + servletMappingList.add(new ServletMapping("/testGet", "testGet", "io.seata.integration.http.MockController")); + servletMappingList.add(new ServletMapping("/testPost", "testPost", "io.seata.integration.http.MockController")); + servletMappingList.add(new ServletMapping("/testException", "testException", "io.seata.integration.http.MockController")); + } + + private String method; + private String path; + private String clazz; + + public ServletMapping(String path, String method, String clazz) { + this.method = method; + this.path = path; + this.clazz = clazz; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getClazz() { + return clazz; + } + + public void setClazz(String clazz) { + this.clazz = clazz; + } +} diff --git a/integration/motan/pom.xml b/integration/motan/pom.xml new file mode 100644 index 0000000..51f89dd --- /dev/null +++ b/integration/motan/pom.xml @@ -0,0 +1,47 @@ + + + + + + io.seata + seata-parent + ${revision} + ../../pom.xml + + 4.0.0 + seata-motan + jar + seata-motan ${project.version} + + + + ${project.groupId} + seata-tm + ${project.version} + + + com.weibo + motan-core + + + com.weibo + motan-transport-netty + + + \ No newline at end of file diff --git a/integration/motan/src/main/java/io/seata/integration/motan/MotanTransactionFilter.java b/integration/motan/src/main/java/io/seata/integration/motan/MotanTransactionFilter.java new file mode 100644 index 0000000..d4a7603 --- /dev/null +++ b/integration/motan/src/main/java/io/seata/integration/motan/MotanTransactionFilter.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.motan; + +import com.weibo.api.motan.common.MotanConstants; +import com.weibo.api.motan.core.extension.Activation; +import com.weibo.api.motan.core.extension.Scope; +import com.weibo.api.motan.core.extension.Spi; +import com.weibo.api.motan.filter.Filter; +import com.weibo.api.motan.rpc.Caller; +import com.weibo.api.motan.rpc.Request; +import com.weibo.api.motan.rpc.Response; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author slievrly + */ +@Spi(scope = Scope.SINGLETON) +@Activation(key = {MotanConstants.NODE_TYPE_SERVICE, MotanConstants.NODE_TYPE_REFERER}, sequence = 100) +public class MotanTransactionFilter implements Filter { + private static final Logger LOGGER = LoggerFactory.getLogger(MotanTransactionFilter.class); + + public MotanTransactionFilter(){} + @Override + public Response filter(final Caller caller, final Request request) { + String currentXid = RootContext.getXID(); + String requestXid = getRpcXid(request); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext [" + currentXid + "] xid in Request [" + requestXid + "]"); + } + boolean bind = false; + if (currentXid != null) { + request.getAttachments().put(RootContext.KEY_XID, currentXid); + } else if (requestXid != null) { + RootContext.bind(requestXid); + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind [" + requestXid + "] to RootContext"); + } + + } + try { + return caller.call(request); + } finally { + if (bind) { + String unbindXid = RootContext.unbind(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind [" + unbindXid + "] from RootContext"); + } + if (!requestXid.equalsIgnoreCase(unbindXid)) { + LOGGER.warn("xid has changed, during RPC from " + requestXid + " to " + unbindXid); + if (unbindXid != null) { + RootContext.bind(unbindXid); + LOGGER.warn("bind [" + unbindXid + "] back to RootContext"); + } + } + } + } + } + + /** + * get rpc xid + * @param request + * @return + */ + private String getRpcXid(Request request) { + String rpcXid = request.getAttachments().get(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = request.getAttachments().get(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/integration/motan/src/main/resources/META-INF/services/com.weibo.api.motan.filter.Filter b/integration/motan/src/main/resources/META-INF/services/com.weibo.api.motan.filter.Filter new file mode 100644 index 0000000..8c062ab --- /dev/null +++ b/integration/motan/src/main/resources/META-INF/services/com.weibo.api.motan.filter.Filter @@ -0,0 +1 @@ +io.seata.integration.motan.MotanTransactionFilter \ No newline at end of file diff --git a/integration/motan/src/test/java/io/seata/integration/motan/MotanTransactionFilterTest.java b/integration/motan/src/test/java/io/seata/integration/motan/MotanTransactionFilterTest.java new file mode 100644 index 0000000..ef05442 --- /dev/null +++ b/integration/motan/src/test/java/io/seata/integration/motan/MotanTransactionFilterTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.motan; + +import com.weibo.api.motan.config.ProtocolConfig; +import com.weibo.api.motan.config.RefererConfig; +import com.weibo.api.motan.config.RegistryConfig; +import com.weibo.api.motan.config.ServiceConfig; +import io.seata.core.context.RootContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author slievrly + */ +class MotanTransactionFilterTest { + + private static final String SERVICE_GROUP = "motan"; + private static final String SERVICE_VERSION = "1.0.0"; + private static final int SERVICE_PORT = 8004; + private static final String PROTOCOL_ID = "motan"; + private static final String PROTOCOL_NAME = "motan"; + private static final String XID = "127.0.0.1:8091:87654321"; + private static final int REQUEST_TIMEOUT = 1000; + + @Test + void testGetProviderXID() { + RootContext.bind(XID); + providerStart(); + consumerStart(); + RootContext.unbind(); + } + + public void providerStart() { + ServiceConfig serviceConfig = new ServiceConfig<>(); + serviceConfig.setInterface(XIDService.class); + serviceConfig.setRef(new XIDServiceImpl()); + serviceConfig.setGroup(SERVICE_GROUP); + serviceConfig.setVersion(SERVICE_VERSION); + RegistryConfig registryConfig = new RegistryConfig(); + registryConfig.setRegProtocol("local"); + registryConfig.setCheck(false); + serviceConfig.setRegistry(registryConfig); + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId(PROTOCOL_ID); + protocol.setName(PROTOCOL_NAME); + serviceConfig.setProtocol(protocol); + serviceConfig.setExport("motan:" + SERVICE_PORT); + serviceConfig.export(); + } + + private void consumerStart() { + RefererConfig refererConfig = new RefererConfig<>(); + refererConfig.setInterface(XIDService.class); + refererConfig.setGroup(SERVICE_GROUP); + refererConfig.setVersion(SERVICE_VERSION); + refererConfig.setRequestTimeout(REQUEST_TIMEOUT); + RegistryConfig registry = new RegistryConfig(); + refererConfig.setRegistry(registry); + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId(PROTOCOL_ID); + protocol.setName(PROTOCOL_NAME); + refererConfig.setProtocol(protocol); + refererConfig.setDirectUrl("localhost:" + SERVICE_PORT); + XIDService service = refererConfig.getRef(); + Assertions.assertEquals(service.getXid(), XID); + } +} diff --git a/integration/motan/src/test/java/io/seata/integration/motan/XIDService.java b/integration/motan/src/test/java/io/seata/integration/motan/XIDService.java new file mode 100644 index 0000000..7afea12 --- /dev/null +++ b/integration/motan/src/test/java/io/seata/integration/motan/XIDService.java @@ -0,0 +1,23 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.motan; + +/** + * @author slievrly + */ +public interface XIDService { + String getXid(); +} diff --git a/integration/motan/src/test/java/io/seata/integration/motan/XIDServiceImpl.java b/integration/motan/src/test/java/io/seata/integration/motan/XIDServiceImpl.java new file mode 100644 index 0000000..0758af2 --- /dev/null +++ b/integration/motan/src/test/java/io/seata/integration/motan/XIDServiceImpl.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.motan; + +import io.seata.core.context.RootContext; + +/** + * @author slievrly + */ +public class XIDServiceImpl implements XIDService { + + @Override + public String getXid() { + return RootContext.getXID(); + } +} diff --git a/integration/sofa-rpc/pom.xml b/integration/sofa-rpc/pom.xml new file mode 100644 index 0000000..cb01cca --- /dev/null +++ b/integration/sofa-rpc/pom.xml @@ -0,0 +1,44 @@ + + + + + io.seata + seata-parent + ${revision} + ../../pom.xml + + 4.0.0 + seata-sofa-rpc + jar + seata-sofa-rpc ${project.version} + + + + ${project.groupId} + seata-tm + ${project.version} + + + com.alipay.sofa + sofa-rpc-all + + + + + diff --git a/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextConsumerFilter.java b/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextConsumerFilter.java new file mode 100644 index 0000000..1463124 --- /dev/null +++ b/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextConsumerFilter.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +import com.alipay.sofa.rpc.context.RpcInternalContext; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.alipay.sofa.rpc.ext.Extension; +import com.alipay.sofa.rpc.filter.AutoActive; +import com.alipay.sofa.rpc.filter.Filter; +import com.alipay.sofa.rpc.filter.FilterInvoker; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TransactionContext on consumer side. + * + * @author Geng Zhang + * @since 0.6.0 + */ +@Extension(value = "transactionContextConsumer") +@AutoActive(consumerSide = true) +public class TransactionContextConsumerFilter extends Filter { + + /** + * Logger for this class + */ + private static final Logger LOGGER = LoggerFactory.getLogger(TransactionContextConsumerFilter.class); + + @Override + public SofaResponse invoke(FilterInvoker filterInvoker, SofaRequest sofaRequest) throws SofaRpcException { + String xid = RootContext.getXID(); + String rpcXid = getRpcXid(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]"); + } + boolean bind = false; + if (xid != null) { + sofaRequest.addRequestProp(RootContext.KEY_XID, xid); + } else { + if (rpcXid != null) { + RootContext.bind(rpcXid); + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind[" + rpcXid + "] to RootContext"); + } + } + } + try { + return filterInvoker.invoke(sofaRequest); + } finally { + if (bind) { + String unbindXid = RootContext.unbind(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind[" + unbindXid + "] from RootContext"); + } + if (!rpcXid.equalsIgnoreCase(unbindXid)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid); + } + if (unbindXid != null) { + RootContext.bind(unbindXid); + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("bind [" + unbindXid + "] back to RootContext"); + } + } + } + } + } + } + + /** + * get rpc xid + * @return + */ + private String getRpcXid() { + String rpcXid = (String) RpcInternalContext.getContext().getAttachment(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = (String) RpcInternalContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextProviderFilter.java b/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextProviderFilter.java new file mode 100644 index 0000000..15bbed9 --- /dev/null +++ b/integration/sofa-rpc/src/main/java/io/seata/integration/sofa/rpc/TransactionContextProviderFilter.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +import com.alipay.sofa.rpc.context.RpcInternalContext; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.alipay.sofa.rpc.ext.Extension; +import com.alipay.sofa.rpc.filter.AutoActive; +import com.alipay.sofa.rpc.filter.Filter; +import com.alipay.sofa.rpc.filter.FilterInvoker; +import io.seata.core.context.RootContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TransactionContext on provider side. + * + * @author Geng Zhang + * @since 0.6.0 + */ +@Extension(value = "transactionContextProvider") +@AutoActive(providerSide = true) +public class TransactionContextProviderFilter extends Filter { + + /** + * Logger for this class + */ + private static final Logger LOGGER = LoggerFactory.getLogger(TransactionContextProviderFilter.class); + + @Override + public SofaResponse invoke(FilterInvoker filterInvoker, SofaRequest sofaRequest) throws SofaRpcException { + String xid = RootContext.getXID(); + String rpcXid = getRpcXid(sofaRequest); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]"); + } + boolean bind = false; + if (xid != null) { + RpcInternalContext.getContext().setAttachment(RootContext.KEY_XID, xid); + } else { + if (rpcXid != null) { + RootContext.bind(rpcXid); + bind = true; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("bind[" + rpcXid + "] to RootContext"); + } + } + } + try { + return filterInvoker.invoke(sofaRequest); + } finally { + if (bind) { + String unbindXid = RootContext.unbind(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("unbind[" + unbindXid + "] from RootContext"); + } + if (!rpcXid.equalsIgnoreCase(unbindXid)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid); + } + if (unbindXid != null) { + RootContext.bind(unbindXid); + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("bind [" + unbindXid + "] back to RootContext"); + } + } + } + } + } + } + + /** + * get rpc xid + * @return + */ + private String getRpcXid(SofaRequest sofaRequest) { + String rpcXid = (String) sofaRequest.getRequestProp(RootContext.KEY_XID); + if (rpcXid == null) { + rpcXid = (String) sofaRequest.getRequestProp(RootContext.KEY_XID.toLowerCase()); + } + return rpcXid; + } + +} diff --git a/integration/sofa-rpc/src/main/resources/META-INF/services/com.alipay.sofa.rpc.filter.Filter b/integration/sofa-rpc/src/main/resources/META-INF/services/com.alipay.sofa.rpc.filter.Filter new file mode 100644 index 0000000..4bbbb33 --- /dev/null +++ b/integration/sofa-rpc/src/main/resources/META-INF/services/com.alipay.sofa.rpc.filter.Filter @@ -0,0 +1,2 @@ +io.seata.integration.sofa.rpc.TransactionContextProviderFilter +io.seata.integration.sofa.rpc.TransactionContextConsumerFilter \ No newline at end of file diff --git a/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloService.java b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloService.java new file mode 100644 index 0000000..18d58a5 --- /dev/null +++ b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloService.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +/** + * @author Geng Zhang + */ +public interface HelloService { + + String sayHello(String name, int age); +} diff --git a/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceImpl.java b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceImpl.java new file mode 100644 index 0000000..38dc798 --- /dev/null +++ b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +import io.seata.core.context.RootContext; + +/** + * @author Geng Zhang + */ +public class HelloServiceImpl implements HelloService { + + private String result; + + private String xid; + + public HelloServiceImpl() { + + } + + public HelloServiceImpl(String result) { + this.result = result; + } + + @Override + public String sayHello(String name, int age) { + xid = RootContext.getXID(); + return result != null ? result : "hello " + name + " from server! age: " + age; + } + + public String getXid() { + return xid; + } +} diff --git a/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceProxy.java b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceProxy.java new file mode 100644 index 0000000..2e593e7 --- /dev/null +++ b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/HelloServiceProxy.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +import io.seata.core.context.RootContext; + +/** + * @author Geng Zhang + */ +public class HelloServiceProxy implements HelloService { + + private String xid; + + private HelloService proxy; + + public HelloServiceProxy(HelloService proxy) { + this.proxy = proxy; + } + + @Override + public String sayHello(String name, int age) { + xid = RootContext.getXID(); + return proxy.sayHello(name, age); + } + + public String getXid() { + return xid; + } +} diff --git a/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/TransactionContextFilterTest.java b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/TransactionContextFilterTest.java new file mode 100644 index 0000000..7060e0c --- /dev/null +++ b/integration/sofa-rpc/src/test/java/io/seata/integration/sofa/rpc/TransactionContextFilterTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.integration.sofa.rpc; + +import com.alipay.sofa.rpc.config.ConsumerConfig; +import com.alipay.sofa.rpc.config.ProviderConfig; +import com.alipay.sofa.rpc.config.ServerConfig; +import com.alipay.sofa.rpc.context.RpcInternalContext; +import com.alipay.sofa.rpc.context.RpcInvokeContext; +import com.alipay.sofa.rpc.context.RpcRunningState; +import com.alipay.sofa.rpc.context.RpcRuntimeContext; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import io.seata.core.context.RootContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +public class TransactionContextFilterTest { + + @Test + public void testAll() { + HelloServiceImpl helloServiceImpl; + HelloService helloServiceRef; + HelloServiceProxy helloServiceProxy; + HelloService helloService; + + // mock A -> B -> C + + { // C + ServerConfig serverConfig1 = new ServerConfig() + .setStopTimeout(0).setPort(22222) + .setQueues(5).setCoreThreads(1).setMaxThreads(1); + helloServiceImpl = new HelloServiceImpl(); + ProviderConfig providerConfig = new ProviderConfig() + .setInterfaceId(HelloService.class.getName()) + .setRef(helloServiceImpl) + .setServer(serverConfig1) + .setUniqueId("x1") + .setRegister(false); + providerConfig.export(); + } + { // B + ConsumerConfig consumerConfig = new ConsumerConfig() + .setInterfaceId(HelloService.class.getName()) + .setTimeout(1000) + .setDirectUrl("bolt://127.0.0.1:22222") + .setUniqueId("x1") + .setRegister(false); + helloServiceRef = consumerConfig.refer(); + + ServerConfig serverConfig2 = new ServerConfig() + .setStopTimeout(0).setPort(22223) + .setQueues(5).setCoreThreads(1).setMaxThreads(1); + helloServiceProxy = new HelloServiceProxy(helloServiceRef); + ProviderConfig providerConfig = new ProviderConfig() + .setInterfaceId(HelloService.class.getName()) + .setRef(helloServiceProxy) + .setServer(serverConfig2) + .setUniqueId("x2") + .setRegister(false); + providerConfig.export(); + } + { // A + ConsumerConfig consumerConfig = new ConsumerConfig() + .setInterfaceId(HelloService.class.getName()) + .setTimeout(1000) + .setDirectUrl("bolt://127.0.0.1:22223") + .setUniqueId("x2") + .setRegister(false); + helloService = consumerConfig.refer(); + } + + try { + helloService.sayHello("xxx", 22); + // check C + Assertions.assertNull(helloServiceImpl.getXid()); + // check B + Assertions.assertNull(helloServiceProxy.getXid()); + } catch (Exception e) { + Assertions.assertTrue(e instanceof SofaRpcException); + } finally { + Assertions.assertNull(RootContext.unbind()); + } + + RootContext.bind("xidddd"); + try { + helloService.sayHello("xxx", 22); + // check C + Assertions.assertEquals(helloServiceImpl.getXid(), "xidddd"); + // check B + Assertions.assertEquals(helloServiceProxy.getXid(), "xidddd"); + } catch (Exception e) { + Assertions.assertTrue(e instanceof SofaRpcException); + } finally { + Assertions.assertEquals("xidddd", RootContext.unbind()); + } + } + + @BeforeAll + public static void adBeforeClass() { + RpcRunningState.setUnitTestMode(true); + } + + @AfterAll + public static void adAfterClass() { + RpcRuntimeContext.destroy(); + RpcInternalContext.removeContext(); + RpcInvokeContext.removeContext(); + } +} \ No newline at end of file diff --git a/metrics/README.md b/metrics/README.md new file mode 100644 index 0000000..630e0b3 --- /dev/null +++ b/metrics/README.md @@ -0,0 +1,131 @@ +### Metrics +#### 设计思路 +1. Seata作为一个被集成的数据一致性框架,Metrics模块将尽可能少的使用第三方依赖以降低发生冲突的风险; +2. Metrics模块将竭力争取更高的度量性能和更低的资源开销,尽可能降低开启后带来的副作用; +3. 配置式,Metrics是否激活、数据如何发布,取决于对应的配置; +4. 不使用Spring,使用SPI(Service Provider Interface)加载扩展; +5. 初始仅发布核心Transaction相关指标,之后结合社区的需求,逐步完善运维方面的所有其他指标。 + +#### 模块说明 +由2个核心API模块`seata-metrics-api`和`seata-metrics-core`,以及N个实现模块例如`seata-metrics-registry-compact`、`seata-metrics-exporter-prometheus`构成: + +- seata-metrics-api模块 + +此模块是Metrics的核心,将作为Seata基础架构的一部分被TC、TM和RM引用,它内部**没有任何具体实现代码**,仅包含接口定义,定义的内容包括: +1. Meter类接口:`Gauge`、`Counter`、`Timer`... +2. 注册容器接口`Registry` +3. Measurement数据导出接口`Exporter` + +>提示:Metrics本身在开源领域也已有很多实现,例如 +>1. [Netflix-Spectator](https://github.com/Netflix/spectator) +>2. [Dropwizard-Metrics](https://github.com/dropwizard/metrics) +>3. [Dubbo-Metrics](https://github.com/dubbo/dubbo-metrics) + +>它们有的轻而敏捷,有的重而强大,由于也是“实现”,因此不会纳入`seata-metrics-api`中,避免实现绑定。 + +- seata-metrics-core模块 + +Metrics核心模块,根据配置组织(加载)1个Registry和N个Exporter; + +- seata-metrics-registry-compact模块 + +这是我们提供的默认(内置)的Registry实现,不使用其它Metrics开源库,轻量级的实现了以下四种Meter: + +| Meter类型 | 描述 | +| --------- | ------------------------------------------------------------ | +| CompactGauge | 单一最新值度量器 | +| CompactCounter | 单一累加度量器,可增可减 | +| CompactSummary | 多Measurement输出计数器,将输出`total`(合计)、`count`(计数)、`max`(最大)、`average`(合计/计数)和`tps`(合计/时间间隔),无单位 | +| CompactTimer | 多Measurement输出计时器,将输出`total`(合计)、`count`(计数)、`max`(最大)和`average`(合计/计数),支持微秒为单位累计 | + +其中包含的Registry,即`CompactRegistry`,它只有接受measure()方法调用的时候才计算度量值,因此计算窗口完全取决于Exporter的实现,故目前不太适合需要多Exporter的场景使用(如何扩展请参见后文)。 + +>说明: +>1. 未来可能增加更丰富复杂的度量器例如Histogram,这是一种可以本地统计聚合75th, 90th, 95th, 98th, 99th,99.9th...的度量器,适合某些场合,但需要更多内存。 +>2. 所有的计量器都将继承自Meter,所有的计量器执行measure()方法后,都将归一化的生成1或N个Measurement结果。 + +- seata-metrics-exporter-prometheus模块 + +Prometheus发布器`PrometheusExporter`,将度量数据同步给Prometheus。 + +>说明:不同的监控系统,采集度量数据的方式不尽相同,例如Zabbix支持用zabbix-agent推送,Prometheus则推荐使用prometheus-server[拉取](https://prometheus.io/docs/practices/pushing/)的方式;同样数据交换协议也不同,因此往往需要逐一适配。 + +#### 如何使用 +如果需要开启TC的Metrics,需要在其配置中增加配置项: +```text +## metrics settings +metrics { + registry-type = "compact" + # multi exporters use comma divided + exporter-list = "prometheus" + exporter-prometheus-port = 9898 +} +``` + +启动TC,即可在`http://tc-server-ip:9898/metrics`上获取到Metrics的文本格式数据。 + +>提示:默认使用`9898`端口,Prometheus已登记的端口列表[在此](https://github.com/prometheus/prometheus/wiki/Default-port-allocations),如果想更换端口,可通过`metrics.exporter-prometheus-port`配置修改。 + +##### 下载并启动Prometheus +下载完毕后,修改Prometheus的配置文件`prometheus.yml`,在`scrape_configs`中增加一项抓取Seata的度量数据: +```yaml +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'seata' + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ['tc-server-ip:9898'] +``` + +##### 查看数据输出 +推荐结合配置[Grafana](https://prometheus.io/docs/visualization/grafana/)获得更好的查询效果,初期Seata导出的Metrics包括: + +- TC : + +| Metrics | 描述 | +| ------ | --------- | +| seata.transaction(role=tc,meter=counter,status=active/committed/rollback) | 当前活动中/已提交/已回滚的事务总数 | +| seata.transaction(role=tc,meter=summary,statistic=count,status=committed/rollback) | 当前周期内提交/回滚的事务数 | +| seata.transaction(role=tc,meter=summary,statistic=tps,status=committed/rollback) | 当前周期内提交/回滚的事务TPS(transaction per second) | +| seata.transaction(role=tc,meter=timer,statistic=total,status=committed/rollback) | 当前周期内提交/回滚的事务耗时总和 | +| seata.transaction(role=tc,meter=timer,statistic=count,status=committed/rollback) | 当前周期内提交/回滚的事务数 | +| seata.transaction(role=tc,meter=timer,statistic=average,status=committed/rollback) | 当前周期内提交/回滚的事务平均耗时 | +| seata.transaction(role=tc,meter=timer,statistic=max,status=committed/rollback) | 当前周期内提交/回滚的事务最大耗时 | + +>提示:seata.transaction(role=tc,meter=summary,statistic=count,status=committed/rollback)和seata.transaction(role=tc,meter=timer,statistic=count,status=committed/rollback)的值可能相同,但它们来源于两个不同的度量器。 + +- TM: + +稍后实现,包括诸如: +seata.transaction(role=tm,name={GlobalTransactionalName},meter=counter,status=active/committed/rollback) : 以GlobalTransactionalName为维度区分不同Transactional的状态。 + +- RM: + +稍后实现,包括诸如: +seata.transaction(role=rm,name={BranchTransactionalName},mode=at/mt,meter=counter,status=active/committed/rollback):以BranchTransactionalName为维度以及AT/MT维度区分不同分支Transactional的状态。 + +#### 如何扩展 +如果有下面几种情况: + +1. 您不是使用Prometheus作为运维监控系统,但希望能够将Seata的Metrics数据集成进Dashboard中; + +您需要实现新的Exporter,例如如果需要对接Zabbix,创建`seata-metrics-exporter-zabbix`模块,然后在ExporterType中添加新的Exporter类型,最后在`metrics.exporter-list`中配置。 + +2. 您需要更复杂强大的度量器类型,这些度量器在其他Metrics实现库中已有,希望集成这些第三方依赖直接使用; + +您可以不使用内置的CompactRegistry的实现,完全扩展一个新的Registry库,例如希望使用Netflix Spectator的实现,扩展名为`seata-metrics-registry-spectator`的模块,然后在RegistryType中添加新的Registry类型,开发完成后,设置`metrics.registry-type`为对应的类型。 + +3. 您需要改变默认Metric的Measurement输出,例如在Timer中增加一个`min`或`sd`(方差); + +您可以修改对应Meter的实现,包括`measure()`方法返回的Measurement列表。 \ No newline at end of file diff --git a/metrics/pom.xml b/metrics/pom.xml new file mode 100644 index 0000000..a410350 --- /dev/null +++ b/metrics/pom.xml @@ -0,0 +1,38 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + pom + seata-metrics + seata-metrics ${project.version} + + + seata-metrics-all + seata-metrics-api + seata-metrics-core + seata-metrics-registry-compact + seata-metrics-exporter-prometheus + + + \ No newline at end of file diff --git a/metrics/seata-metrics-all/pom.xml b/metrics/seata-metrics-all/pom.xml new file mode 100644 index 0000000..6f1e2f2 --- /dev/null +++ b/metrics/seata-metrics-all/pom.xml @@ -0,0 +1,52 @@ + + + + + seata-metrics + io.seata + ${revision} + + 4.0.0 + seata-metrics-all + seata-metrics-all ${project.version} + + + + ${project.groupId} + seata-metrics-api + ${project.version} + + + ${project.groupId} + seata-metrics-core + ${project.version} + + + ${project.groupId} + seata-metrics-registry-compact + ${project.version} + + + ${project.groupId} + seata-metrics-exporter-prometheus + ${project.version} + + + + \ No newline at end of file diff --git a/metrics/seata-metrics-api/pom.xml b/metrics/seata-metrics-api/pom.xml new file mode 100644 index 0000000..0926855 --- /dev/null +++ b/metrics/seata-metrics-api/pom.xml @@ -0,0 +1,29 @@ + + + + + seata-metrics + io.seata + ${revision} + + 4.0.0 + + seata-metrics-api + seata-metrics-api ${project.version} + \ No newline at end of file diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Clock.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Clock.java new file mode 100644 index 0000000..27fd8ae --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Clock.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Clock interface for metrics + * + * @author zhengyangyong + */ +public interface Clock { + double getCurrentMilliseconds(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Counter.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Counter.java new file mode 100644 index 0000000..fa8d1f7 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Counter.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Counter interface for metrics + * + * @author zhengyangyong + */ +public interface Counter extends Meter { + long increase(long value); + + long decrease(long value); + + long get(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Gauge.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Gauge.java new file mode 100644 index 0000000..b5913dc --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Gauge.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Gauge interface for metrics + * + * @author zhengyangyong + */ +public interface Gauge extends Meter { + T get(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Id.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Id.java new file mode 100644 index 0000000..32cfb68 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Id.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.UUID; + +/** + * Meter id + * + * @author zhengyangyong + */ +public class Id { + private final UUID id; + + private final String name; + + private final SortedMap tags; + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public Iterable> getTags() { + return tags.entrySet(); + } + + public int getTagCount() { + return tags.size(); + } + + public Id(String name) { + this.id = UUID.randomUUID(); + this.name = name; + this.tags = new TreeMap<>(); + } + + public Id withTag(String name, String value) { + this.tags.put(name, value); + return this; + } + + public Id withTag(Iterable> tags) { + if (tags != null) { + for (Entry tag : tags) { + this.tags.put(tag.getKey(), tag.getValue()); + } + } + return this; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(name); + builder.append("("); + if (tags.size() == 0) { + builder.append(")"); + return builder.toString(); + } + for (Entry tag : tags.entrySet()) { + builder.append(String.format("%s=%s,", tag.getKey(), tag.getValue())); + } + builder.delete(builder.length() - 1, builder.length()); + builder.append(")"); + return builder.toString(); + } +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/IdConstants.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/IdConstants.java new file mode 100644 index 0000000..a1445c2 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/IdConstants.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Seata metrics constants for id + * + * @author zhengyangyong + */ +public interface IdConstants { + String SEATA_TRANSACTION = "seata.transaction"; + + String APP_ID_KEY = "applicationId"; + + String GROUP_KEY = "group"; + + String NAME_KEY = "name"; + + String ROLE_KEY = "role"; + + String METER_KEY = "meter"; + + String STATISTIC_KEY = "statistic"; + + String STATUS_KEY = "status"; + + String ROLE_VALUE_TC = "tc"; + + String ROLE_VALUE_TM = "tm"; + + String ROLE_VALUE_RM = "rm"; + + String METER_VALUE_GAUGE = "gauge"; + + String METER_VALUE_COUNTER = "counter"; + + String METER_VALUE_SUMMARY = "summary"; + + String METER_VALUE_TIMER = "timer"; + + String STATISTIC_VALUE_COUNT = "count"; + + String STATISTIC_VALUE_TOTAL = "total"; + + String STATISTIC_VALUE_TPS = "tps"; + + String STATISTIC_VALUE_MAX = "max"; + + String STATISTIC_VALUE_AVERAGE = "average"; + + String STATUS_VALUE_ACTIVE = "active"; + + String STATUS_VALUE_COMMITTED = "committed"; + + String STATUS_VALUE_ROLLBACKED = "rollbacked"; +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Measurement.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Measurement.java new file mode 100644 index 0000000..544473e --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Measurement.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Value of meter + * + * @author zhengyangyong + */ +public class Measurement { + private final Id id; + + private final double timestamp; + + private final double value; + + public Id getId() { + return id; + } + + public double getTimestamp() { + return timestamp; + } + + public double getValue() { + return value; + } + + public Measurement(Id id, double timestamp, double value) { + this.id = id; + this.timestamp = timestamp; + this.value = value; + } +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Meter.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Meter.java new file mode 100644 index 0000000..6da5e1b --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Meter.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Meter interface for metrics + * + * @author zhengyangyong + */ +public interface Meter { + Id getId(); + + Iterable measure(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Summary.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Summary.java new file mode 100644 index 0000000..0ffcbe3 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Summary.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Summary interface for metrics + * + * @author zhengyangyong + */ +public interface Summary extends Meter { + default void increase() { + increase(1); + } + + void increase(long value); + + long total(); + + long count(); + + double tps(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/SystemClock.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/SystemClock.java new file mode 100644 index 0000000..b5084f8 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/SystemClock.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +/** + * Default clock implement use system + * + * @author zhengyangyong + */ +public class SystemClock implements Clock { + public static final Clock INSTANCE = new SystemClock(); + + @Override + public double getCurrentMilliseconds() { + return System.currentTimeMillis(); + } +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Timer.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Timer.java new file mode 100644 index 0000000..de6c6f2 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/Timer.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics; + +import java.util.concurrent.TimeUnit; + +/** + * Default clock implement use system + * + * @author zhengyangyong + */ +public interface Timer extends Meter { + void record(long value, TimeUnit unit); + + long count(); + + long total(); + + long max(); + + double average(); +} diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/exporter/Exporter.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/exporter/Exporter.java new file mode 100644 index 0000000..75a5d56 --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/exporter/Exporter.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.exporter; + +import java.io.Closeable; + +import io.seata.metrics.registry.Registry; + +/** + * Exporter interface for metrics + * + * @author zhengyangyong + */ +public interface Exporter extends Closeable { + void setRegistry(Registry registry); +} \ No newline at end of file diff --git a/metrics/seata-metrics-api/src/main/java/io/seata/metrics/registry/Registry.java b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/registry/Registry.java new file mode 100644 index 0000000..9efaf5a --- /dev/null +++ b/metrics/seata-metrics-api/src/main/java/io/seata/metrics/registry/Registry.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry; + +import java.util.function.Supplier; + +import io.seata.metrics.Counter; +import io.seata.metrics.Gauge; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.Summary; +import io.seata.metrics.Timer; + +/** + * Registry interface for metrics + * + * @author zhengyangyong + */ +public interface Registry { + Gauge getGauge(Id id, Supplier supplier); + + Counter getCounter(Id id); + + Summary getSummary(Id id); + + Timer getTimer(Id id); + + Iterable measure(); +} diff --git a/metrics/seata-metrics-core/pom.xml b/metrics/seata-metrics-core/pom.xml new file mode 100644 index 0000000..eb8e5a1 --- /dev/null +++ b/metrics/seata-metrics-core/pom.xml @@ -0,0 +1,43 @@ + + + + + seata-metrics + io.seata + ${revision} + + 4.0.0 + + seata-metrics-core + seata-metrics-core ${project.version} + + + + io.seata + seata-metrics-api + ${project.parent.version} + + + ${project.groupId} + seata-core + ${project.version} + + + + \ No newline at end of file diff --git a/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterFactory.java b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterFactory.java new file mode 100644 index 0000000..e21a0dd --- /dev/null +++ b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.exporter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Exporter Factory for load all configured exporters + * + * @author zhengyangyong + */ +public class ExporterFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(ExporterFactory.class); + + public static List getInstanceList() { + List exporters = new ArrayList<>(); + String exporterTypeNameList = ConfigurationFactory.getInstance().getConfig( + ConfigurationKeys.METRICS_PREFIX + ConfigurationKeys.METRICS_EXPORTER_LIST, null); + if (!StringUtils.isNullOrEmpty(exporterTypeNameList)) { + String[] exporterTypeNames = exporterTypeNameList.split(","); + for (String exporterTypeName : exporterTypeNames) { + ExporterType exporterType; + try { + exporterType = ExporterType.getType(exporterTypeName); + exporters.add( + EnhancedServiceLoader.load(Exporter.class, Objects.requireNonNull(exporterType).getName())); + } catch (Exception exx) { + LOGGER.error("not support metrics exporter type: {}",exporterTypeName, exx); + } + } + } + return exporters; + } +} diff --git a/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterType.java b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterType.java new file mode 100644 index 0000000..a147a4d --- /dev/null +++ b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/exporter/ExporterType.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.exporter; + +/** + * Supported metrics exporter type + * + * @author zhengyangyong + */ +public enum ExporterType { + /** + * Export metrics data to Prometheus + */ + PROMETHEUS("prometheus"); + + private String name; + + ExporterType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static ExporterType getType(String name) { + if (PROMETHEUS.name().equalsIgnoreCase(name)) { + return PROMETHEUS; + } else { + throw new IllegalArgumentException("not support exporter type: " + name); + } + } +} diff --git a/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryFactory.java b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryFactory.java new file mode 100644 index 0000000..e864eb4 --- /dev/null +++ b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry; + +import java.util.Objects; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; + +/** + * Registry Factory for load configured metrics registry + * + * @author zhengyangyong + */ +public class RegistryFactory { + public static Registry getInstance() { + RegistryType registryType; + String registryTypeName = ConfigurationFactory.getInstance().getConfig( + ConfigurationKeys.METRICS_PREFIX + ConfigurationKeys.METRICS_REGISTRY_TYPE, null); + if (!StringUtils.isNullOrEmpty(registryTypeName)) { + try { + registryType = RegistryType.getType(registryTypeName); + } catch (Exception exx) { + throw new NotSupportYetException("not support metrics registry type: " + registryTypeName); + } + return EnhancedServiceLoader.load(Registry.class, Objects.requireNonNull(registryType).getName()); + } + return null; + } +} diff --git a/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryType.java b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryType.java new file mode 100644 index 0000000..6c3f5bc --- /dev/null +++ b/metrics/seata-metrics-core/src/main/java/io/seata/metrics/registry/RegistryType.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry; + +/** + * Supported metrics registry type + * + * @author zhengyangyong + */ +public enum RegistryType { + /** + * Built-in compact metrics registry + */ + COMPACT("compact"); + + private String name; + + RegistryType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static RegistryType getType(String name) { + if (COMPACT.name().equalsIgnoreCase(name)) { + return COMPACT; + } else { + throw new IllegalArgumentException("not support registry type: " + name); + } + } +} diff --git a/metrics/seata-metrics-exporter-prometheus/pom.xml b/metrics/seata-metrics-exporter-prometheus/pom.xml new file mode 100644 index 0000000..f6b4105 --- /dev/null +++ b/metrics/seata-metrics-exporter-prometheus/pom.xml @@ -0,0 +1,54 @@ + + + + + seata-metrics + io.seata + ${revision} + + 4.0.0 + + seata-metrics-exporter-prometheus + seata-metrics-exporter-prometheus ${project.version} + + + 0.6.0 + + + + + ${project.groupId} + seata-metrics-api + ${project.parent.version} + + + + ${project.groupId} + seata-core + ${project.parent.version} + + + + io.prometheus + simpleclient_httpserver + ${prometheus.client.version} + + + + \ No newline at end of file diff --git a/metrics/seata-metrics-exporter-prometheus/src/main/java/io/seata/metrics/exporter/prometheus/PrometheusExporter.java b/metrics/seata-metrics-exporter-prometheus/src/main/java/io/seata/metrics/exporter/prometheus/PrometheusExporter.java new file mode 100644 index 0000000..36ffbbd --- /dev/null +++ b/metrics/seata-metrics-exporter-prometheus/src/main/java/io/seata/metrics/exporter/prometheus/PrometheusExporter.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.exporter.prometheus; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import io.prometheus.client.Collector; +import io.prometheus.client.Collector.MetricFamilySamples.Sample; +import io.prometheus.client.exporter.HTTPServer; +import io.seata.common.loader.LoadLevel; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.metrics.Measurement; +import io.seata.metrics.exporter.Exporter; +import io.seata.metrics.registry.Registry; + +import static io.seata.core.constants.ConfigurationKeys.METRICS_EXPORTER_PROMETHEUS_PORT; + +/** + * Exporter for Prometheus + * + * @author zhengyangyong + */ +@LoadLevel(name = "prometheus", order = 1) +public class PrometheusExporter extends Collector implements Collector.Describable, Exporter { + + private final HTTPServer server; + + private Registry registry; + + public PrometheusExporter() throws IOException { + int port = ConfigurationFactory.getInstance().getInt( + ConfigurationKeys.METRICS_PREFIX + METRICS_EXPORTER_PROMETHEUS_PORT, 9898); + this.server = new HTTPServer(port, true); + this.register(); + } + + @Override + public void setRegistry(Registry registry) { + this.registry = registry; + } + + @Override + public List collect() { + List familySamples = new ArrayList<>(); + if (registry != null) { + Iterable measurements = registry.measure(); + List samples = new ArrayList<>(); + measurements.forEach(measurement -> samples.add(convertMeasurementToSample(measurement))); + + if (!samples.isEmpty()) { + familySamples.add(new MetricFamilySamples("seata", Type.UNTYPED, "seata", samples)); + } + } + return familySamples; + } + + private Sample convertMeasurementToSample(Measurement measurement) { + String prometheusName = measurement.getId().getName().replace(".", "_"); + List labelNames = new ArrayList<>(); + List labelValues = new ArrayList<>(); + for (Entry tag : measurement.getId().getTags()) { + labelNames.add(tag.getKey()); + labelValues.add(tag.getValue()); + } + return new Sample(prometheusName, labelNames, labelValues, measurement.getValue(), + (long)measurement.getTimestamp()); + } + + @Override + public List describe() { + return collect(); + } + + @Override + public void close() { + server.stop(); + } + +} \ No newline at end of file diff --git a/metrics/seata-metrics-exporter-prometheus/src/main/resources/META-INF/services/io.seata.metrics.exporter.Exporter b/metrics/seata-metrics-exporter-prometheus/src/main/resources/META-INF/services/io.seata.metrics.exporter.Exporter new file mode 100644 index 0000000..ee06c43 --- /dev/null +++ b/metrics/seata-metrics-exporter-prometheus/src/main/resources/META-INF/services/io.seata.metrics.exporter.Exporter @@ -0,0 +1 @@ +io.seata.metrics.exporter.prometheus.PrometheusExporter \ No newline at end of file diff --git a/metrics/seata-metrics-registry-compact/pom.xml b/metrics/seata-metrics-registry-compact/pom.xml new file mode 100644 index 0000000..48b5dd5 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/pom.xml @@ -0,0 +1,43 @@ + + + + + seata-metrics + io.seata + ${revision} + + 4.0.0 + + seata-metrics-registry-compact + seata-metrics-registry-compact ${project.version} + + + + io.seata + seata-metrics-api + ${project.parent.version} + + + io.seata + seata-common + ${project.parent.version} + + + + \ No newline at end of file diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactCounter.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactCounter.java new file mode 100644 index 0000000..a8ac6f2 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactCounter.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; + +import io.seata.metrics.Clock; +import io.seata.metrics.Counter; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.SystemClock; + +/** + * Compact Counter implement with AtomicLong + * + * @author zhengyangyong + */ +public class CompactCounter implements Counter { + private final Id id; + + private final AtomicLong counter; + + private final Clock clock; + + public CompactCounter(Id id) { + this(id, SystemClock.INSTANCE); + } + + public CompactCounter(Id id, Clock clock) { + this.id = id; + this.counter = new AtomicLong(0); + this.clock = clock; + } + + @Override + public Id getId() { + return id; + } + + @Override + public long increase(long value) { + return counter.addAndGet(value); + } + + @Override + public long decrease(long value) { + return increase(-1 * value); + } + + @Override + public long get() { + return counter.get(); + } + + @Override + public Iterable measure() { + return Collections.singletonList(new Measurement(id, clock.getCurrentMilliseconds(), counter.get())); + } +} \ No newline at end of file diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactGauge.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactGauge.java new file mode 100644 index 0000000..b7d9e32 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactGauge.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.Collections; +import java.util.function.Supplier; + +import io.seata.metrics.Clock; +import io.seata.metrics.Gauge; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.SystemClock; + +/** + * Compact Gauge implement with Supplier + * + * @author zhengyangyong + */ +public class CompactGauge implements Gauge { + private final Id id; + + private final Supplier supplier; + + private final Clock clock; + + public CompactGauge(Id id, Supplier supplier) { + this(id, supplier, SystemClock.INSTANCE); + } + + public CompactGauge(Id id, Supplier supplier, Clock clock) { + this.id = id; + this.supplier = supplier; + this.clock = clock; + } + + @Override + public T get() { + return supplier.get(); + } + + @Override + public Id getId() { + return id; + } + + @Override + public Iterable measure() { + return Collections.singletonList(new Measurement(id, clock.getCurrentMilliseconds(), get().doubleValue())); + } +} diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactRegistry.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactRegistry.java new file mode 100644 index 0000000..4487b4e --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactRegistry.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.metrics.Counter; +import io.seata.metrics.Gauge; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.Meter; +import io.seata.metrics.registry.Registry; +import io.seata.metrics.Summary; +import io.seata.metrics.Timer; + +/** + * Compact Registry implement, this registry only compute all Measurements when call measure method and do not cache + * + * @author zhengyangyong + */ +@LoadLevel(name = "compact", order = 1) +public class CompactRegistry implements Registry { + private static final Map METERS = new ConcurrentHashMap<>(); + + @Override + public Gauge getGauge(Id id, Supplier supplier) { + return (Gauge)CollectionUtils.computeIfAbsent(METERS, id.getId(), key -> new CompactGauge<>(id, supplier)); + } + + @Override + public Counter getCounter(Id id) { + return (Counter)CollectionUtils.computeIfAbsent(METERS, id.getId(), key -> new CompactCounter(id)); + } + + @Override + public Summary getSummary(Id id) { + return (Summary)CollectionUtils.computeIfAbsent(METERS, id.getId(), key -> new CompactSummary(id)); + } + + @Override + public Timer getTimer(Id id) { + return (Timer)CollectionUtils.computeIfAbsent(METERS, id.getId(), key -> new CompactTimer(id)); + } + + @Override + public Iterable measure() { + final List measurements = new ArrayList<>(); + if (METERS.isEmpty()) { + return measurements; + } + METERS.values().iterator() + .forEachRemaining(meter -> meter.measure().forEach(measurements::add)); + return measurements; + } +} diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactSummary.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactSummary.java new file mode 100644 index 0000000..db77fa1 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactSummary.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.Arrays; + +import io.seata.metrics.Clock; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.Summary; +import io.seata.metrics.SystemClock; +import io.seata.metrics.IdConstants; + +/** + * Compact Summary implement with SummaryValue + * + * @author zhengyangyong + */ +public class CompactSummary implements Summary { + private final Id id; + + private final Id countId; + + private final Id totalId; + + private final Id tpsId; + + private volatile SummaryValue value; + + private final Clock clock; + + public CompactSummary(Id id) { + this(id, SystemClock.INSTANCE); + } + + public CompactSummary(Id id, Clock clock) { + this.id = id; + this.countId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_COUNT); + this.totalId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_TOTAL); + this.tpsId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_TPS); + this.value = new SummaryValue(clock.getCurrentMilliseconds()); + this.clock = clock; + } + + @Override + public Id getId() { + return id; + } + + @Override + public void increase(long value) { + this.value.increase(value); + } + + @Override + public long total() { + return this.value.getTotal(); + } + + @Override + public long count() { + return this.value.getCount(); + } + + @Override + public double tps() { + return this.value.getTps(clock.getCurrentMilliseconds()); + } + + @Override + public Iterable measure() { + SummaryValue value = this.value; + double time = clock.getCurrentMilliseconds(); + this.value = new SummaryValue(time); + return Arrays.asList(new Measurement(countId, time, value.getCount()), + new Measurement(totalId, time, value.getTotal()), + new Measurement(tpsId, time, value.getTps(time))); + } +} diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactTimer.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactTimer.java new file mode 100644 index 0000000..4c09313 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/CompactTimer.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import io.seata.metrics.Clock; +import io.seata.metrics.Id; +import io.seata.metrics.Measurement; +import io.seata.metrics.SystemClock; +import io.seata.metrics.Timer; +import io.seata.metrics.IdConstants; + +/** + * Compact Timer implement with TimerValue + * + * @author zhengyangyong + */ +public class CompactTimer implements Timer { + private final Id id; + + private final Id countId; + + private final Id totalId; + + private final Id maxId; + + private final Id averageId; + + private volatile TimerValue value; + + private final Clock clock; + + public CompactTimer(Id id) { + this(id, SystemClock.INSTANCE); + } + + public CompactTimer(Id id, Clock clock) { + this.id = id; + this.countId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_COUNT); + this.totalId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_TOTAL); + this.maxId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_MAX); + this.averageId = new Id(id.getName()).withTag(id.getTags()) + .withTag(IdConstants.STATISTIC_KEY, IdConstants.STATISTIC_VALUE_AVERAGE); + this.value = new TimerValue(); + this.clock = clock; + } + + @Override + public Id getId() { + return id; + } + + @Override + public void record(long value, TimeUnit unit) { + this.value.record(value, unit); + } + + @Override + public long count() { + return this.value.getCount(); + } + + @Override + public long total() { + return this.value.getTotal(); + } + + @Override + public long max() { + return this.value.getMax(); + } + + @Override + public double average() { + return this.value.getAverage(); + } + + @Override + public Iterable measure() { + //reset value when measure + double time = clock.getCurrentMilliseconds(); + TimerValue value = this.value; + this.value = new TimerValue(); + return Arrays.asList(new Measurement(countId, time, value.getCount()), + new Measurement(totalId, time, value.getTotal() * 0.001), + new Measurement(maxId, time, value.getMax() * 0.001), + new Measurement(averageId, time, value.getAverage() * 0.001)); + } +} diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/SummaryValue.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/SummaryValue.java new file mode 100644 index 0000000..dc65a8e --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/SummaryValue.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.concurrent.atomic.LongAdder; + +/** + * Record container for CompactSummary + * + * @author zhengyangyong + */ +public class SummaryValue { + private final LongAdder count; + + private final LongAdder total; + + private final double startMilliseconds; + + public long getCount() { + return count.longValue(); + } + + public long getTotal() { + return total.longValue(); + } + + public double getTps(double currentMilliseconds) { + if (currentMilliseconds <= startMilliseconds) { + return 0; + } + return total.doubleValue() / (currentMilliseconds - startMilliseconds) * 1000.0; + } + + public SummaryValue(double startMilliseconds) { + this.count = new LongAdder(); + this.total = new LongAdder(); + this.startMilliseconds = startMilliseconds; + } + + public void increase() { + this.increase(1); + } + + public void increase(long value) { + if (value < 0) { + return; + } + this.count.increment(); + this.total.add(value); + } +} diff --git a/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/TimerValue.java b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/TimerValue.java new file mode 100644 index 0000000..9b7d6ed --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/java/io/seata/metrics/registry/compact/TimerValue.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.metrics.registry.compact; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +/** + * Record container for CompactTimer + * + * @author zhengyangyong + */ +public class TimerValue { + private final LongAdder count; + + private final LongAdder total; + + private final AtomicLong max; + + public long getCount() { + return count.longValue(); + } + + public long getTotal() { + return total.longValue(); + } + + public long getMax() { + return max.get(); + } + + public double getAverage() { + double count = this.count.doubleValue(); + double total = this.total.doubleValue(); + return count == 0 ? 0 : total / count; + } + + public TimerValue() { + this.count = new LongAdder(); + this.total = new LongAdder(); + this.max = new AtomicLong(0); + } + + public void record(long value, TimeUnit unit) { + if (value < 0) { + return; + } + long changeValue = unit == TimeUnit.MICROSECONDS ? value : TimeUnit.MICROSECONDS.convert(value, unit); + this.count.increment(); + this.total.add(changeValue); + this.max.accumulateAndGet(changeValue, Math::max); + } +} \ No newline at end of file diff --git a/metrics/seata-metrics-registry-compact/src/main/resources/META-INF/services/io.seata.metrics.registry.Registry b/metrics/seata-metrics-registry-compact/src/main/resources/META-INF/services/io.seata.metrics.registry.Registry new file mode 100644 index 0000000..83dfd94 --- /dev/null +++ b/metrics/seata-metrics-registry-compact/src/main/resources/META-INF/services/io.seata.metrics.registry.Registry @@ -0,0 +1 @@ +io.seata.metrics.registry.compact.CompactRegistry \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bbd6c05 --- /dev/null +++ b/mvnw @@ -0,0 +1,224 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..0dd1731 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,140 @@ +@REM ---------------------------------------------------------------------------- +@REM Copyright 1999-2019 Seata.io Group. +@REM +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0214d69 --- /dev/null +++ b/pom.xml @@ -0,0 +1,534 @@ + + + + 4.0.0 + + io.seata + seata-parent + ${revision} + + all + bom + common + config + core + discovery + distribution + integration/dubbo + integration/dubbo-alibaba + integration/sofa-rpc + integration/motan + integration/grpc + integration/http + rm + rm-datasource + server + spring + tcc + test + tm + metrics + serializer + seata-spring-boot-starter + compressor + saga + sqlparser + + pom + + Seata Parent POM ${project.version} + http://seata.io + top seata project pom.xml file + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4.2 + + 1.8 + 1.8 + UTF-8 + UTF-8 + + false + 0.8.3 + 1.1.0 + 2.22.2 + 3.6.0 + 1.3.6 + 3.8 + 3.0.0 + 2.2.1 + 0.5.0 + 3.0 + 3.1.1 + + + 5.4.2 + 2.23.4 + 3.12.2 + 1.4.2 + + + true + latest + + true + true + + 5.1.35 + 8.0.19 + + + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + + org.junit.platform + junit-platform-launcher + ${junit-platform-launcher.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + + + + io.seata + seata-bom + ${revision} + pom + import + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-maven-plugin.version} + + ${project.basedir}/src/main/resources/protobuf/io/seata/protocol/transcation/ + + com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier} + + + + + + compile + + + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + ${project.build.sourceEncoding} + 2 + true + + rulesets/java/ali-comment.xml + rulesets/java/ali-concurrent.xml + rulesets/java/ali-constant.xml + rulesets/java/ali-exception.xml + rulesets/java/ali-flowcontrol.xml + rulesets/java/ali-naming.xml + rulesets/java/ali-oop.xml + rulesets/java/ali-orm.xml + rulesets/java/ali-other.xml + rulesets/java/ali-set.xml + + + **/generated/*.java + **/antlr/mysql/parser/*.* + **/antlr/mysql/antlr/*.* + **/antlr/mysql/stream/ANTLRNoCaseStringStream.java + + + + + verify + + check + + + + + + com.alibaba.p3c + p3c-pmd + ${p3c-pmd.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + + prepare-agent + + + + report + test + + report + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.codehaus.mojo + flatten-maven-plugin + ${flatten-maven-plugin.version} + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M3 + + + enforce-maven + + enforce + + + + + [3.6.0,) + + + + + + + + + + + + + + fantaibao-fantaibao-maven-repository + maven-repository + https://fantaibao-maven.pkg.coding.net/repository/fantaibao/maven-repository/ + + + diff --git a/rm-datasource/pom.xml b/rm-datasource/pom.xml new file mode 100644 index 0000000..4972c71 --- /dev/null +++ b/rm-datasource/pom.xml @@ -0,0 +1,129 @@ + + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-rm-datasource + jar + seata-rm-datasource ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + ${project.groupId} + seata-rm + ${project.version} + + + ${project.groupId} + seata-sqlparser-core + ${project.version} + + + ${project.groupId} + seata-compressor-all + ${project.version} + + + com.github.ben-manes.caffeine + caffeine + + + io.protostuff + protostuff-core + true + + + io.protostuff + protostuff-runtime + true + + + com.fasterxml.jackson.core + jackson-databind + + + + org.apache.commons + commons-dbcp2 + test + + + com.h2database + h2 + test + + + ${project.groupId} + seata-sqlparser-druid + ${project.version} + test + + + + com.google.guava + guava + + + + com.esotericsoftware + kryo + provided + true + + + de.javakaffee + kryo-serializers + provided + true + + + de.ruedigermoeller + fst + provided + true + + + com.alibaba + fastjson + provided + true + + + com.alibaba + druid + provided + true + + + mysql + mysql-connector-java + test + + + diff --git a/rm-datasource/src/main/java/io/seata/rm/BaseDataSourceResource.java b/rm-datasource/src/main/java/io/seata/rm/BaseDataSourceResource.java new file mode 100644 index 0000000..575dbd5 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/BaseDataSourceResource.java @@ -0,0 +1,193 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.rm.datasource.SeataDataSourceProxy; +import io.seata.rm.datasource.xa.Holdable; +import io.seata.rm.datasource.xa.Holder; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Driver; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +/** + * Base class of those DataSources working as Seata Resource. + * + * @author sharajava + */ +public abstract class BaseDataSourceResource implements SeataDataSourceProxy, Resource, Holder { + + protected DataSource dataSource; + + protected String resourceId; + + protected String resourceGroupId; + + protected BranchType branchType; + + protected String dbType; + + protected Driver driver; + + private ConcurrentHashMap keeper = new ConcurrentHashMap<>(); + + /** + * Gets target data source. + * + * @return the target data source + */ + @Override + public DataSource getTargetDataSource() { + return dataSource; + } + + @Override + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + @Override + public String getResourceGroupId() { + return resourceGroupId; + } + + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + @Override + public BranchType getBranchType() { + return branchType; + } + + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + public String getDbType() { + return dbType; + } + + public void setDbType(String dbType) { + this.dbType = dbType; + } + + public Driver getDriver() { + return driver; + } + + public void setDriver(Driver driver) { + this.driver = driver; + } + + + @Override + public T unwrap(Class iface) throws SQLException { + if (iface == null) { + return null; + } + + if (iface.isInstance(this)) { + return (T) this; + } + + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isInstance(this); + + } + + protected void dataSourceCheck() { + if (dataSource == null) { + throw new UnsupportedOperationException("dataSource CAN NOT be null"); + } + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + dataSourceCheck(); + return dataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + dataSourceCheck(); + dataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + dataSourceCheck(); + dataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + dataSourceCheck(); + return dataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + dataSourceCheck(); + return dataSource.getParentLogger(); + } + + @Override + public T hold(String key, T value) { + if (value.isHeld()) { + T x = keeper.get(key); + if (x != value) { + throw new ShouldNeverHappenException("something wrong with keeper, keeping[" + x + + "] but[" + value + "] is also kept with the same key[" + key + "]"); + } + return value; + } + T x = keeper.put(key, value); + value.setHeld(true); + return x; + } + + @Override + public T release(String key, T value) { + T x = keeper.remove(key); + if (x != value) { + throw new ShouldNeverHappenException("something wrong with keeper, released[" + x + + "] but[" + value + "] is wanted with key[" + key + "]"); + } + value.setHeld(false); + return x; + } + + @Override + public T lookup(String key) { + return keeper.get(key); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/GlobalLockExecutor.java b/rm-datasource/src/main/java/io/seata/rm/GlobalLockExecutor.java new file mode 100644 index 0000000..3ddf98e --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/GlobalLockExecutor.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.model.GlobalLockConfig; + +/** + * executor to execute business logic that require global lock + * @author selfishlover + */ +public interface GlobalLockExecutor { + + /** + * execute business logic + * @return business return + * @throws Throwable whatever throw during execution + */ + Object execute() throws Throwable; + + /** + * global lock config info + * @return + */ + GlobalLockConfig getGlobalLockConfig(); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/GlobalLockTemplate.java b/rm-datasource/src/main/java/io/seata/rm/GlobalLockTemplate.java new file mode 100644 index 0000000..4985f5d --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/GlobalLockTemplate.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.context.GlobalLockConfigHolder; +import io.seata.core.context.RootContext; +import io.seata.core.model.GlobalLockConfig; + +/** + * executor template for local transaction which need global lock + * @author selfishlover + */ +public class GlobalLockTemplate { + + public Object execute(GlobalLockExecutor executor) throws Throwable { + boolean alreadyInGlobalLock = RootContext.requireGlobalLock(); + if (!alreadyInGlobalLock) { + RootContext.bindGlobalLockFlag(); + } + + // set my config to config holder so that it can be access in further execution + // for example, LockRetryController can access it with config holder + GlobalLockConfig myConfig = executor.getGlobalLockConfig(); + GlobalLockConfig previousConfig = GlobalLockConfigHolder.setAndReturnPrevious(myConfig); + + try { + return executor.execute(); + } finally { + // only unbind when this is the root caller. + // otherwise, the outer caller would lose global lock flag + if (!alreadyInGlobalLock) { + RootContext.unbindGlobalLockFlag(); + } + + // if previous config is not null, we need to set it back + // so that the outer logic can still use their config + if (previousConfig != null) { + GlobalLockConfigHolder.setAndReturnPrevious(previousConfig); + } else { + GlobalLockConfigHolder.remove(); + } + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/RMHandlerAT.java b/rm-datasource/src/main/java/io/seata/rm/RMHandlerAT.java new file mode 100644 index 0000000..2c68b29 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/RMHandlerAT.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Calendar; +import java.util.Date; + +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; +import io.seata.rm.datasource.DataSourceManager; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.undo.UndoLogManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Rm handler at. + * + * @author sharajava + */ +public class RMHandlerAT extends AbstractRMHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(RMHandlerAT.class); + + private static final int LIMIT_ROWS = 3000; + + @Override + public void handle(UndoLogDeleteRequest request) { + DataSourceManager dataSourceManager = (DataSourceManager)getResourceManager(); + DataSourceProxy dataSourceProxy = dataSourceManager.get(request.getResourceId()); + if (dataSourceProxy == null) { + LOGGER.warn("Failed to get dataSourceProxy for delete undolog on {}", request.getResourceId()); + return; + } + Date logCreatedSave = getLogCreated(request.getSaveDays()); + Connection conn = null; + try { + conn = dataSourceProxy.getPlainConnection(); + int deleteRows = 0; + do { + try { + deleteRows = UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()) + .deleteUndoLogByLogCreated(logCreatedSave, LIMIT_ROWS, conn); + if (deleteRows > 0 && !conn.getAutoCommit()) { + conn.commit(); + } + } catch (SQLException exx) { + if (deleteRows > 0 && !conn.getAutoCommit()) { + conn.rollback(); + } + throw exx; + } + } while (deleteRows == LIMIT_ROWS); + } catch (Exception e) { + LOGGER.error("Failed to delete expired undo_log, error:{}", e.getMessage(), e); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException closeEx) { + LOGGER.warn("Failed to close JDBC resource while deleting undo_log ", closeEx); + } + } + } + } + + private Date getLogCreated(int saveDays) { + if (saveDays <= 0) { + saveDays = UndoLogDeleteRequest.DEFAULT_SAVE_DAYS; + } + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DATE, -saveDays); + return calendar.getTime(); + } + + /** + * get AT resource managerDataSourceManager.java + * + * @return + */ + @Override + protected ResourceManager getResourceManager() { + return DefaultResourceManager.get().getResourceManager(BranchType.AT); + } + + @Override + public BranchType getBranchType() { + return BranchType.AT; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/RMHandlerXA.java b/rm-datasource/src/main/java/io/seata/rm/RMHandlerXA.java new file mode 100644 index 0000000..8a6b0dc --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/RMHandlerXA.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; + +/** + * The type RM handler XA. + * + * @author sharajava + */ +public class RMHandlerXA extends AbstractRMHandler { + + @Override + protected ResourceManager getResourceManager() { + return DefaultResourceManager.get().getResourceManager(BranchType.XA); + } + + @Override + public BranchType getBranchType() { + return BranchType.XA; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractConnectionProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractConnectionProxy.java new file mode 100644 index 0000000..ef8c810 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractConnectionProxy.java @@ -0,0 +1,380 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Struct; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +/** + * The type Abstract connection proxy. + * + * @author sharajava + */ +public abstract class AbstractConnectionProxy implements Connection { + + /** + * The Data source proxy. + */ + protected DataSourceProxy dataSourceProxy; + + /** + * The Target connection. + */ + protected Connection targetConnection; + + /** + * Instantiates a new Abstract connection proxy. + * + * @param dataSourceProxy the data source proxy + * @param targetConnection the target connection + */ + public AbstractConnectionProxy(DataSourceProxy dataSourceProxy, Connection targetConnection) { + this.dataSourceProxy = dataSourceProxy; + this.targetConnection = targetConnection; + } + + /** + * Gets data source proxy. + * + * @return the data source proxy + */ + public DataSourceProxy getDataSourceProxy() { + return dataSourceProxy; + } + + /** + * Gets target connection. + * + * @return the target connection + */ + public Connection getTargetConnection() { + return targetConnection; + } + + /** + * Gets db type. + * + * @return the db type + */ + public String getDbType() { + return dataSourceProxy.getDbType(); + } + + @Override + public Statement createStatement() throws SQLException { + Statement targetStatement = getTargetConnection().createStatement(); + return new StatementProxy(this, targetStatement); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + String dbType = getDbType(); + // support oracle 10.2+ + PreparedStatement targetPreparedStatement = null; + if (BranchType.AT == RootContext.getBranchType()) { + List sqlRecognizers = SQLVisitorFactory.get(sql, dbType); + if (sqlRecognizers != null && sqlRecognizers.size() == 1) { + SQLRecognizer sqlRecognizer = sqlRecognizers.get(0); + if (sqlRecognizer != null && sqlRecognizer.getSQLType() == SQLType.INSERT) { + TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dbType).getTableMeta(getTargetConnection(), + sqlRecognizer.getTableName(), getDataSourceProxy().getResourceId()); + String[] pkNameArray = new String[tableMeta.getPrimaryKeyOnlyName().size()]; + tableMeta.getPrimaryKeyOnlyName().toArray(pkNameArray); + targetPreparedStatement = getTargetConnection().prepareStatement(sql,pkNameArray); + } + } + } + if (targetPreparedStatement == null) { + targetPreparedStatement = getTargetConnection().prepareStatement(sql); + } + return new PreparedStatementProxy(this, targetPreparedStatement, sql); + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return targetConnection.prepareCall(sql); + } + + @Override + public String nativeSQL(String sql) throws SQLException { + return targetConnection.nativeSQL(sql); + } + + @Override + public boolean getAutoCommit() throws SQLException { + return targetConnection.getAutoCommit(); + } + + @Override + public void close() throws SQLException { + targetConnection.close(); + } + + @Override + public boolean isClosed() throws SQLException { + return targetConnection.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return targetConnection.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + targetConnection.setReadOnly(readOnly); + + } + + @Override + public boolean isReadOnly() throws SQLException { + return targetConnection.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + targetConnection.setCatalog(catalog); + + } + + @Override + public String getCatalog() throws SQLException { + return targetConnection.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + targetConnection.setTransactionIsolation(level); + + } + + @Override + public int getTransactionIsolation() throws SQLException { + return targetConnection.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return targetConnection.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + targetConnection.clearWarnings(); + + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + Statement statement = targetConnection.createStatement(resultSetType, resultSetConcurrency); + return new StatementProxy(this, statement); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + PreparedStatement preparedStatement = targetConnection.prepareStatement(sql, resultSetType, + resultSetConcurrency); + return new PreparedStatementProxy(this, preparedStatement, sql); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return targetConnection.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + @Override + public Map> getTypeMap() throws SQLException { + return targetConnection.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + targetConnection.setTypeMap(map); + + } + + @Override + public void setHoldability(int holdability) throws SQLException { + targetConnection.setHoldability(holdability); + + } + + @Override + public int getHoldability() throws SQLException { + return targetConnection.getHoldability(); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + Statement statement = targetConnection.createStatement(resultSetType, resultSetConcurrency, + resultSetHoldability); + return new StatementProxy(this, statement); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + PreparedStatement preparedStatement = targetConnection.prepareStatement(sql, resultSetType, + resultSetConcurrency, resultSetHoldability); + return new PreparedStatementProxy(this, preparedStatement, sql); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return targetConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + PreparedStatement preparedStatement = targetConnection.prepareStatement(sql, autoGeneratedKeys); + return new PreparedStatementProxy(this, preparedStatement, sql); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + PreparedStatement preparedStatement = targetConnection.prepareStatement(sql, columnIndexes); + return new PreparedStatementProxy(this, preparedStatement, sql); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + PreparedStatement preparedStatement = targetConnection.prepareStatement(sql, columnNames); + return new PreparedStatementProxy(this, preparedStatement, sql); + } + + @Override + public Clob createClob() throws SQLException { + return targetConnection.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return targetConnection.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return targetConnection.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return targetConnection.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return targetConnection.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + targetConnection.setClientInfo(name, value); + + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + targetConnection.setClientInfo(properties); + + } + + @Override + public String getClientInfo(String name) throws SQLException { + return targetConnection.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return targetConnection.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return targetConnection.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return targetConnection.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + targetConnection.setSchema(schema); + + } + + @Override + public String getSchema() throws SQLException { + return targetConnection.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + targetConnection.abort(executor); + + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + targetConnection.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return targetConnection.getNetworkTimeout(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return targetConnection.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return targetConnection.isWrapperFor(iface); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceCacheResourceManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceCacheResourceManager.java new file mode 100644 index 0000000..4180b38 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceCacheResourceManager.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.common.executor.Initialize; +import io.seata.core.model.Resource; +import io.seata.rm.AbstractResourceManager; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Abstract RM with DataSource Cache. + * + * @author sharajava + */ +public abstract class AbstractDataSourceCacheResourceManager extends AbstractResourceManager implements Initialize { + + protected Map dataSourceCache = new ConcurrentHashMap<>(); + + /** + * Instantiates a new Data source manager. + */ + public AbstractDataSourceCacheResourceManager() { + } + + @Override + public abstract void init(); + + @Override + public Map getManagedResources() { + return dataSourceCache; + } + + @Override + public void registerResource(Resource resource) { + dataSourceCache.put(resource.getResourceId(), resource); + super.registerResource(resource); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceProxy.java new file mode 100644 index 0000000..ccee902 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractDataSourceProxy.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +/** + * The type Abstract data source proxy. + * + * @author sharajava + */ +public abstract class AbstractDataSourceProxy implements SeataDataSourceProxy { + + /** + * The Target data source. + */ + protected DataSource targetDataSource; + + /** + * Instantiates a new Abstract data source proxy. + */ + public AbstractDataSourceProxy(){} + + /** + * Instantiates a new Abstract data source proxy. + * + * @param targetDataSource the target data source + */ + public AbstractDataSourceProxy(DataSource targetDataSource) { + this.targetDataSource = targetDataSource; + } + + /** + * Gets target data source. + * + * @return the target data source + */ + @Override + public DataSource getTargetDataSource() { + return targetDataSource; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return targetDataSource.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return targetDataSource.isWrapperFor(iface); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return targetDataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + targetDataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + targetDataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return targetDataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return targetDataSource.getParentLogger(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractPreparedStatementProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractPreparedStatementProxy.java new file mode 100644 index 0000000..708769c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractPreparedStatementProxy.java @@ -0,0 +1,418 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.common.util.CollectionUtils; +import io.seata.sqlparser.struct.Null; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The type Abstract prepared statement proxy. + * + * @author sharajava + */ +public abstract class AbstractPreparedStatementProxy extends StatementProxy + implements PreparedStatement { + + /** + * The Parameters. + */ + protected Map> parameters; + + private void initParameterHolder() { + this.parameters = new HashMap<>(); + } + + /** + * Instantiates a new Abstract prepared statement proxy. + * + * @param connectionProxy the connection proxy + * @param targetStatement the target statement + * @param targetSQL the target sql + * @throws SQLException the sql exception + */ + public AbstractPreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement, + String targetSQL) throws SQLException { + super(connectionProxy, targetStatement, targetSQL); + initParameterHolder(); + } + + /** + * Instantiates a new Abstract prepared statement proxy. + * + * @param connectionProxy the connection proxy + * @param targetStatement the target statement + * @throws SQLException the sql exception + */ + public AbstractPreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement) + throws SQLException { + super(connectionProxy, targetStatement); + initParameterHolder(); + } + + /** + * Gets params by index. + * + * @param index the index + * @return the params by index + */ + public List getParamsByIndex(int index) { + return parameters.get(index); + } + + /** + * Sets param by index. + * + * @param index the index + * @param x the x + */ + protected void setParamByIndex(int index, Object x) { + CollectionUtils.computeIfAbsent(parameters, index, e -> new ArrayList<>()) + .add(x); + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + setParamByIndex(parameterIndex, Null.get()); + targetStatement.setNull(parameterIndex, sqlType); + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBoolean(parameterIndex, x); + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setByte(parameterIndex, x); + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setShort(parameterIndex, x); + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setInt(parameterIndex, x); + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setLong(parameterIndex, x); + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setFloat(parameterIndex, x); + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setDouble(parameterIndex, x); + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBigDecimal(parameterIndex, x); + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setString(parameterIndex, x); + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBytes(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setDate(parameterIndex, x); + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setTime(parameterIndex, x); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setTimestamp(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setAsciiStream(parameterIndex, x, length); + } + + @Deprecated + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setUnicodeStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void clearParameters() throws SQLException { + initParameterHolder(); + targetStatement.clearParameters(); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setObject(parameterIndex, x); + } + + @Override + public void addBatch() throws SQLException { + targetStatement.addBatch(); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setRef(parameterIndex, x); + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBlob(parameterIndex, x); + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setClob(parameterIndex, x); + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setArray(parameterIndex, x); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return targetStatement.getMetaData(); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setDate(parameterIndex, x, cal); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setTime(parameterIndex, x, cal); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setTimestamp(parameterIndex, x, cal); + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + setParamByIndex(parameterIndex, Null.get()); + targetStatement.setNull(parameterIndex, sqlType, typeName); + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setURL(parameterIndex, x); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return targetStatement.getParameterMetaData(); + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setRowId(parameterIndex, x); + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + setParamByIndex(parameterIndex, value); + targetStatement.setNString(parameterIndex, value); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + setParamByIndex(parameterIndex, value); + targetStatement.setNCharacterStream(parameterIndex, value, length); + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + setParamByIndex(parameterIndex, value); + targetStatement.setNClob(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setClob(parameterIndex, reader, length); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + setParamByIndex(parameterIndex, inputStream); + targetStatement.setBlob(parameterIndex, inputStream, length); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setNClob(parameterIndex, reader, length); + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + setParamByIndex(parameterIndex, xmlObject); + targetStatement.setSQLXML(parameterIndex, xmlObject); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setAsciiStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + setParamByIndex(parameterIndex, x); + targetStatement.setBinaryStream(parameterIndex, x); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setCharacterStream(parameterIndex, reader); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + setParamByIndex(parameterIndex, value); + targetStatement.setNCharacterStream(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setClob(parameterIndex, reader); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + setParamByIndex(parameterIndex, inputStream); + targetStatement.setBlob(parameterIndex, inputStream); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + setParamByIndex(parameterIndex, reader); + targetStatement.setNClob(parameterIndex, reader); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractStatementProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractStatementProxy.java new file mode 100644 index 0000000..32b132c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AbstractStatementProxy.java @@ -0,0 +1,294 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetProvider; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +/** + * The type Abstract statement proxy. + * + * @author sharajava + * + * @param the type parameter + */ +public abstract class AbstractStatementProxy implements Statement { + + /** + * The Connection proxy. + */ + protected AbstractConnectionProxy connectionProxy; + + /** + * The Target statement. + */ + protected T targetStatement; + + /** + * The Target sql. + */ + protected String targetSQL; + + /** + * Instantiates a new Abstract statement proxy. + * + * @param connectionProxy the connection proxy + * @param targetStatement the target statement + * @param targetSQL the target sql + * @throws SQLException the sql exception + */ + public AbstractStatementProxy(AbstractConnectionProxy connectionProxy, T targetStatement, String targetSQL) + throws SQLException { + this.connectionProxy = connectionProxy; + this.targetStatement = targetStatement; + this.targetSQL = targetSQL; + } + + /** + * Instantiates a new Abstract statement proxy. + * + * @param connectionProxy the connection proxy + * @param targetStatement the target statement + * @throws SQLException the sql exception + */ + public AbstractStatementProxy(ConnectionProxy connectionProxy, T targetStatement) throws SQLException { + this(connectionProxy, targetStatement, null); + } + + /** + * Gets connection proxy. + * + * @return the connection proxy + */ + public AbstractConnectionProxy getConnectionProxy() { + return connectionProxy; + } + + /** + * Gets target statement. + * + * @return the target statement + */ + public T getTargetStatement() { + return targetStatement; + } + + /** + * Gets target sql. + * + * @return the target sql + */ + public String getTargetSQL() { + return targetSQL; + } + + @Override + public void close() throws SQLException { + targetStatement.close(); + + } + + @Override + public int getMaxFieldSize() throws SQLException { + return targetStatement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + targetStatement.setMaxFieldSize(max); + + } + + @Override + public int getMaxRows() throws SQLException { + return targetStatement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + targetStatement.setMaxRows(max); + + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + targetStatement.setEscapeProcessing(enable); + + } + + @Override + public int getQueryTimeout() throws SQLException { + return targetStatement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + targetStatement.setQueryTimeout(seconds); + + } + + @Override + public void cancel() throws SQLException { + targetStatement.cancel(); + + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return targetStatement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + targetStatement.clearWarnings(); + + } + + @Override + public void setCursorName(String name) throws SQLException { + targetStatement.setCursorName(name); + + } + + @Override + public ResultSet getResultSet() throws SQLException { + return targetStatement.getResultSet(); + } + + @Override + public int getUpdateCount() throws SQLException { + return targetStatement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return targetStatement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + targetStatement.setFetchDirection(direction); + + } + + @Override + public int getFetchDirection() throws SQLException { + return targetStatement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + targetStatement.setFetchSize(rows); + + } + + @Override + public int getFetchSize() throws SQLException { + return targetStatement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return targetStatement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return targetStatement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + targetStatement.addBatch(sql); + + } + + @Override + public void clearBatch() throws SQLException { + targetStatement.clearBatch(); + targetSQL = null; + } + + @Override + public int[] executeBatch() throws SQLException { + return targetStatement.executeBatch(); + } + + @Override + public Connection getConnection() throws SQLException { + return targetStatement.getConnection(); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return targetStatement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + ResultSet rs = targetStatement.getGeneratedKeys(); + CachedRowSet generatedKeysRowSet = RowSetProvider.newFactory().createCachedRowSet(); + generatedKeysRowSet.populate(rs); + return generatedKeysRowSet; + } + + @Override + public int getResultSetHoldability() throws SQLException { + return targetStatement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return targetStatement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + targetStatement.setPoolable(poolable); + + } + + @Override + public boolean isPoolable() throws SQLException { + return targetStatement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + targetStatement.closeOnCompletion(); + + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return targetStatement.isCloseOnCompletion(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return targetStatement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return targetStatement.isWrapperFor(iface); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/AsyncWorker.java b/rm-datasource/src/main/java/io/seata/rm/datasource/AsyncWorker.java new file mode 100644 index 0000000..c95a0f0 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/AsyncWorker.java @@ -0,0 +1,207 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import com.google.common.collect.Lists; +import io.seata.common.thread.NamedThreadFactory; +import io.seata.config.ConfigurationFactory; +import io.seata.core.model.BranchStatus; +import io.seata.rm.datasource.undo.UndoLogManager; +import io.seata.rm.datasource.undo.UndoLogManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.core.constants.ConfigurationKeys.CLIENT_ASYNC_COMMIT_BUFFER_LIMIT; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_ASYNC_COMMIT_BUFFER_LIMIT; + +/** + * The type Async worker. + * + * @author sharajava + */ +public class AsyncWorker { + + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncWorker.class); + + private static final int DEFAULT_RESOURCE_SIZE = 16; + + private static final int UNDOLOG_DELETE_LIMIT_SIZE = 1000; + + private static final int ASYNC_COMMIT_BUFFER_LIMIT = ConfigurationFactory.getInstance().getInt( + CLIENT_ASYNC_COMMIT_BUFFER_LIMIT, DEFAULT_CLIENT_ASYNC_COMMIT_BUFFER_LIMIT); + + private final DataSourceManager dataSourceManager; + + private final BlockingQueue commitQueue; + + private final ScheduledExecutorService scheduledExecutor; + + public AsyncWorker(DataSourceManager dataSourceManager) { + this.dataSourceManager = dataSourceManager; + + LOGGER.info("Async Commit Buffer Limit: {}", ASYNC_COMMIT_BUFFER_LIMIT); + commitQueue = new LinkedBlockingQueue<>(ASYNC_COMMIT_BUFFER_LIMIT); + + ThreadFactory threadFactory = new NamedThreadFactory("AsyncWorker", 2, true); + scheduledExecutor = new ScheduledThreadPoolExecutor(2, threadFactory); + scheduledExecutor.scheduleAtFixedRate(this::doBranchCommitSafely, 10, 1000, TimeUnit.MILLISECONDS); + } + + public BranchStatus branchCommit(String xid, long branchId, String resourceId) { + Phase2Context context = new Phase2Context(xid, branchId, resourceId); + addToCommitQueue(context); + return BranchStatus.PhaseTwo_Committed; + } + + /** + * try add context to commitQueue directly, if fail(which means the queue is full), + * then doBranchCommit urgently(so that the queue could be empty again) and retry this process. + */ + private void addToCommitQueue(Phase2Context context) { + if (commitQueue.offer(context)) { + return; + } + CompletableFuture.runAsync(this::doBranchCommitSafely, scheduledExecutor) + .thenRun(() -> addToCommitQueue(context)); + } + + void doBranchCommitSafely() { + try { + doBranchCommit(); + } catch (Throwable e) { + LOGGER.error("Exception occur when doing branch commit", e); + } + } + + private void doBranchCommit() { + if (commitQueue.isEmpty()) { + return; + } + + // transfer all context currently received to this list + List allContexts = new LinkedList<>(); + commitQueue.drainTo(allContexts); + + // group context by their resourceId + Map> groupedContexts = groupedByResourceId(allContexts); + + groupedContexts.forEach(this::dealWithGroupedContexts); + } + + Map> groupedByResourceId(List contexts) { + Map> groupedContexts = new HashMap<>(DEFAULT_RESOURCE_SIZE); + contexts.forEach(context -> { + List group = groupedContexts.computeIfAbsent(context.resourceId, key -> new LinkedList<>()); + group.add(context); + }); + return groupedContexts; + } + + private void dealWithGroupedContexts(String resourceId, List contexts) { + DataSourceProxy dataSourceProxy = dataSourceManager.get(resourceId); + if (dataSourceProxy == null) { + LOGGER.warn("Failed to find resource for {}", resourceId); + return; + } + + Connection conn; + try { + conn = dataSourceProxy.getPlainConnection(); + } catch (SQLException sqle) { + LOGGER.error("Failed to get connection for async committing on {}", resourceId, sqle); + return; + } + + UndoLogManager undoLogManager = UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()); + + // split contexts into several lists, with each list contain no more element than limit size + List> splitByLimit = Lists.partition(contexts, UNDOLOG_DELETE_LIMIT_SIZE); + splitByLimit.forEach(partition -> deleteUndoLog(conn, undoLogManager, partition)); + } + + private void deleteUndoLog(Connection conn, UndoLogManager undoLogManager, List contexts) { + Set xids = new LinkedHashSet<>(contexts.size()); + Set branchIds = new LinkedHashSet<>(contexts.size()); + contexts.forEach(context -> { + xids.add(context.xid); + branchIds.add(context.branchId); + }); + + try { + undoLogManager.batchDeleteUndoLog(xids, branchIds, conn); + if (!conn.getAutoCommit()) { + conn.commit(); + } + } catch (SQLException e) { + LOGGER.error("Failed to batch delete undo log", e); + try { + conn.rollback(); + } catch (SQLException rollbackEx) { + LOGGER.error("Failed to rollback JDBC resource after deleting undo log failed", rollbackEx); + } + } finally { + try { + conn.close(); + } catch (SQLException closeEx) { + LOGGER.error("Failed to close JDBC resource after deleting undo log", closeEx); + } + } + } + + static class Phase2Context { + + /** + * AT Phase 2 context + * @param xid the xid + * @param branchId the branch id + * @param resourceId the resource id + */ + public Phase2Context(String xid, long branchId, String resourceId) { + this.xid = xid; + this.branchId = branchId; + this.resourceId = resourceId; + } + + /** + * The Xid. + */ + String xid; + /** + * The Branch id. + */ + long branchId; + /** + * The Resource id. + */ + String resourceId; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/ColumnUtils.java b/rm-datasource/src/main/java/io/seata/rm/datasource/ColumnUtils.java new file mode 100644 index 0000000..505973d --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/ColumnUtils.java @@ -0,0 +1,266 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.rm.datasource.undo.KeywordCheckerFactory; +import io.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; + +/** + * column utils + * + * @author jsbxyyx + */ +public final class ColumnUtils { + + private static final String DOT = "."; + + /** + * The escape + */ + public enum Escape { + /** + * standard escape + */ + STANDARD('"'), + /** + * mysql series escape + */ + MYSQL('`'); + /** + * The Value. + */ + public final char value; + + Escape(char value) { + this.value = value; + } + } + + /** + * del escape by db type + * + * @param cols the cols + * @param dbType the db type + * @return list + */ + public static List delEscape(List cols, String dbType) { + // sql standard + // https://db.apache.org/derby/docs/10.1/ref/crefsqlj1003454.html + // https://docs.oracle.com/javadb/10.8.3.0/ref/crefsqlj1003454.html + // https://www.informit.com/articles/article.aspx?p=2036581&seqNum=2 + List newCols = delEscape(cols, Escape.STANDARD); + if (isMysqlSeries(dbType)) { + newCols = delEscape(newCols, Escape.MYSQL); + } + return newCols; + } + + /** + * del escape + * + * @param cols the cols + * @param escape the escape + * @return delete the column list element left and right escape. + */ + public static List delEscape(List cols, Escape escape) { + if (CollectionUtils.isEmpty(cols)) { + return cols; + } + List newCols = new ArrayList<>(cols.size()); + for (int i = 0, len = cols.size(); i < len; i++) { + String col = cols.get(i); + col = delEscape(col, escape); + newCols.add(col); + } + return newCols; + } + + /** + * del escape by db type + * + * @param colName the column name + * @param dbType the db type + * @return string string + */ + public static String delEscape(String colName, String dbType) { + String newColName = delEscape(colName, Escape.STANDARD); + if (isMysqlSeries(dbType)) { + newColName = delEscape(newColName, Escape.MYSQL); + } + return newColName; + } + + /** + * del escape by escape + * + * @param colName the column name + * @param escape the escape + * @return string string + */ + public static String delEscape(String colName, Escape escape) { + if (colName == null || colName.isEmpty()) { + return colName; + } + + if (colName.charAt(0) == escape.value && colName.charAt(colName.length() - 1) == escape.value) { + // like "scheme"."id" `scheme`.`id` + String str = escape.value + DOT + escape.value; + int index = colName.indexOf(str); + if (index > -1) { + return colName.substring(1, index) + DOT + colName.substring(index + str.length(), colName.length() - 1); + } + return colName.substring(1, colName.length() - 1); + } else { + // like "scheme".id `scheme`.id + String str = escape.value + DOT; + int index = colName.indexOf(str); + if (index > -1 && colName.charAt(0) == escape.value) { + return colName.substring(1, index) + DOT + colName.substring(index + str.length()); + } + // like scheme."id" scheme.`id` + str = DOT + escape.value; + index = colName.indexOf(str); + if (index > -1 && colName.charAt(colName.length() - 1) == escape.value) { + return colName.substring(0, index) + DOT + colName.substring(index + str.length(), colName.length() - 1); + } + } + return colName; + } + + /** + * if necessary, add escape by db type + *
    +     * mysql:
    +     *   only deal with keyword.
    +     * postgresql:
    +     *   only deal with keyword, contains uppercase character.
    +     * oracle:
    +     *   only deal with keyword, not full uppercase character.
    +     * 
    + * + * @param cols the column name list + * @param dbType the db type + * @return list list + */ + public static List addEscape(List cols, String dbType) { + if (CollectionUtils.isEmpty(cols)) { + return cols; + } + List newCols = new ArrayList<>(cols.size()); + for (int i = 0, len = cols.size(); i < len; i++) { + String col = cols.get(i); + col = addEscape(col, dbType); + newCols.add(col); + } + return newCols; + } + + /** + * if necessary, add escape by db type + * + * @param colName the column name + * @param dbType the db type + * @return the colName left and right add escape + */ + public static String addEscape(String colName, String dbType) { + if (isMysqlSeries(dbType)) { + return addEscape(colName, dbType, ColumnUtils.Escape.MYSQL); + } + return addEscape(colName, dbType, ColumnUtils.Escape.STANDARD); + } + + /** + * if necessary, add escape + * + * @param colName the column name + * @param escape the escape + * @return + */ + private static String addEscape(String colName, String dbType, Escape escape) { + if (colName == null || colName.isEmpty()) { + return colName; + } + if (colName.charAt(0) == escape.value && colName.charAt(colName.length() - 1) == escape.value) { + return colName; + } + + KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(dbType); + if (keywordChecker != null) { + boolean check = keywordChecker.checkEscape(colName); + if (!check) { + return colName; + } + } + + if (colName.contains(DOT)) { + // like "scheme".id `scheme`.id + String str = escape.value + DOT; + int dotIndex = colName.indexOf(str); + if (dotIndex > -1) { + return new StringBuilder() + .append(colName.substring(0, dotIndex + str.length())) + .append(escape.value) + .append(colName.substring(dotIndex + str.length())) + .append(escape.value).toString(); + } + // like scheme."id" scheme.`id` + str = DOT + escape.value; + dotIndex = colName.indexOf(str); + if (dotIndex > -1) { + return new StringBuilder() + .append(escape.value) + .append(colName.substring(0, dotIndex)) + .append(escape.value) + .append(colName.substring(dotIndex)) + .toString(); + } + + str = DOT; + dotIndex = colName.indexOf(str); + if (dotIndex > -1) { + return new StringBuilder() + .append(escape.value) + .append(colName.substring(0, dotIndex)) + .append(escape.value) + .append(DOT) + .append(escape.value) + .append(colName.substring(dotIndex + str.length())) + .append(escape.value).toString(); + } + } + + char[] buf = new char[colName.length() + 2]; + buf[0] = escape.value; + buf[buf.length - 1] = escape.value; + + colName.getChars(0, colName.length(), buf, 1); + + return new String(buf).intern(); + } + + private static boolean isMysqlSeries(String dbType) { + return StringUtils.equalsIgnoreCase(dbType, JdbcConstants.MYSQL) || + StringUtils.equalsIgnoreCase(dbType, JdbcConstants.H2) || + StringUtils.equalsIgnoreCase(dbType, JdbcConstants.MARIADB); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionContext.java b/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionContext.java new file mode 100644 index 0000000..a5fd74c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionContext.java @@ -0,0 +1,345 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.SQLException; +import java.sql.Savepoint; +import java.util.Set; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.undo.SQLUndoLog; + +/** + * The type Connection context. + * + * @author sharajava + */ +public class ConnectionContext { + private static final Savepoint DEFAULT_SAVEPOINT = new Savepoint() { + @Override + public int getSavepointId() throws SQLException { + return 0; + } + + @Override + public String getSavepointName() throws SQLException { + return "DEFAULT_SEATA_SAVEPOINT"; + } + }; + + private String xid; + private Long branchId; + private boolean isGlobalLockRequire; + private Savepoint currentSavepoint = DEFAULT_SAVEPOINT; + private boolean autoCommitChanged; + + /** + * the lock keys buffer + */ + private final Map> lockKeysBuffer = new LinkedHashMap<>(); + /** + * the undo items buffer + */ + private final Map> sqlUndoItemsBuffer = new LinkedHashMap<>(); + + private final List savepoints = new ArrayList<>(8); + + /** + * whether requires global lock in this connection + * + * @return + */ + boolean isGlobalLockRequire() { + return isGlobalLockRequire; + } + + /** + * set whether requires global lock in this connection + * + * @param isGlobalLockRequire + */ + void setGlobalLockRequire(boolean isGlobalLockRequire) { + this.isGlobalLockRequire = isGlobalLockRequire; + } + + /** + * Append lock key. + * + * @param lockKey the lock key + */ + void appendLockKey(String lockKey) { + lockKeysBuffer.computeIfAbsent(currentSavepoint, k -> new HashSet<>()).add(lockKey); + } + + /** + * Append undo item. + * + * @param sqlUndoLog the sql undo log + */ + void appendUndoItem(SQLUndoLog sqlUndoLog) { + sqlUndoItemsBuffer.computeIfAbsent(currentSavepoint, k -> new ArrayList<>()).add(sqlUndoLog); + } + + /** + * Append savepoint + * @param savepoint the savepoint + */ + void appendSavepoint(Savepoint savepoint) { + savepoints.add(savepoint); + this.currentSavepoint = savepoint; + } + + public void removeSavepoint(Savepoint savepoint) { + List afterSavepoints = getAfterSavepoints(savepoint); + + if (null == savepoint) { + sqlUndoItemsBuffer.clear(); + lockKeysBuffer.clear(); + } else { + + for (Savepoint sp : afterSavepoints) { + sqlUndoItemsBuffer.remove(sp); + lockKeysBuffer.remove(sp); + } + } + + savepoints.removeAll(afterSavepoints); + currentSavepoint = savepoints.size() == 0 ? DEFAULT_SAVEPOINT : savepoints.get(savepoints.size() - 1); + } + + public void releaseSavepoint(Savepoint savepoint) { + List afterSavepoints = getAfterSavepoints(savepoint); + savepoints.removeAll(afterSavepoints); + currentSavepoint = savepoints.size() == 0 ? DEFAULT_SAVEPOINT : savepoints.get(savepoints.size() - 1); + + // move the undo items & lock keys to current savepoint + for (Savepoint sp : afterSavepoints) { + List savepointSQLUndoLogs = sqlUndoItemsBuffer.remove(sp); + if (CollectionUtils.isNotEmpty(savepointSQLUndoLogs)) { + sqlUndoItemsBuffer.computeIfAbsent(currentSavepoint, k -> new ArrayList<>(savepointSQLUndoLogs.size())) + .addAll(savepointSQLUndoLogs); + } + + Set savepointLockKeys = lockKeysBuffer.remove(sp); + if (CollectionUtils.isNotEmpty(savepointLockKeys)) { + lockKeysBuffer.computeIfAbsent(currentSavepoint, k -> new HashSet<>()) + .addAll(savepointLockKeys); + } + } + } + + /** + * In global transaction boolean. + * + * @return the boolean + */ + public boolean inGlobalTransaction() { + return xid != null; + } + + /** + * Is branch registered boolean. + * + * @return the boolean + */ + public boolean isBranchRegistered() { + return branchId != null; + } + + /** + * Bind. + * + * @param xid the xid + */ + void bind(String xid) { + if (xid == null) { + throw new IllegalArgumentException("xid should not be null"); + } + if (!inGlobalTransaction()) { + setXid(xid); + } else { + if (!this.xid.equals(xid)) { + throw new ShouldNeverHappenException(); + } + } + } + + /** + * Has undo log boolean. + * + * @return the boolean + */ + public boolean hasUndoLog() { + return !sqlUndoItemsBuffer.isEmpty(); + } + + /** + * Gets lock keys buffer. + * + * @return the lock keys buffer + */ + public boolean hasLockKey() { + return !lockKeysBuffer.isEmpty(); + } + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public Long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + void setBranchId(Long branchId) { + this.branchId = branchId; + } + + /** + * is seata change targetConnection autoCommit + * + * @return the boolean + */ + public boolean isAutoCommitChanged() { + return this.autoCommitChanged; + } + + /** + * set seata change targetConnection autoCommit record + * + * @param autoCommitChanged the boolean + */ + public void setAutoCommitChanged(boolean autoCommitChanged) { + this.autoCommitChanged = autoCommitChanged; + } + + + /** + * Reset. + */ + public void reset() { + this.reset(null); + } + + /** + * Reset. + * + * @param xid the xid + */ + void reset(String xid) { + this.xid = xid; + branchId = null; + this.isGlobalLockRequire = false; + savepoints.clear(); + lockKeysBuffer.clear(); + sqlUndoItemsBuffer.clear(); + this.autoCommitChanged = false; + } + + /** + * Build lock keys string. + * + * @return the string + */ + public String buildLockKeys() { + if (lockKeysBuffer.isEmpty()) { + return null; + } + Set lockKeysBufferSet = new HashSet<>(); + for (Set lockKeys : lockKeysBuffer.values()) { + lockKeysBufferSet.addAll(lockKeys); + } + + if (lockKeysBufferSet.isEmpty()) { + return null; + } + + StringBuilder appender = new StringBuilder(); + Iterator iterable = lockKeysBufferSet.iterator(); + while (iterable.hasNext()) { + appender.append(iterable.next()); + if (iterable.hasNext()) { + appender.append(";"); + } + } + return appender.toString(); + } + + /** + * Gets undo items. + * + * @return the undo items + */ + public List getUndoItems() { + List undoItems = new ArrayList<>(); + for (List items : sqlUndoItemsBuffer.values()) { + undoItems.addAll(items); + } + return undoItems; + } + + + /** + * Get the savepoints after target savepoint(include the param savepoint) + * @param savepoint the target savepoint + * @return after savepoints + */ + private List getAfterSavepoints(Savepoint savepoint) { + if (null == savepoint) { + return new ArrayList<>(savepoints); + } + + return new ArrayList<>(savepoints.subList(savepoints.indexOf(savepoint), savepoints.size())); + } + + @Override + public String toString() { + return "ConnectionContext [xid=" + xid + ", branchId=" + branchId + ", lockKeysBuffer=" + lockKeysBuffer + + ", sqlUndoItemsBuffer=" + sqlUndoItemsBuffer + "]"; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionProxy.java new file mode 100644 index 0000000..784a4be --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/ConnectionProxy.java @@ -0,0 +1,363 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.util.concurrent.Callable; + +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.datasource.exec.LockConflictException; +import io.seata.rm.datasource.exec.LockRetryController; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoLogManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_REPORT_RETRY_COUNT; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE; + +/** + * The type Connection proxy. + * + * @author sharajava + */ +public class ConnectionProxy extends AbstractConnectionProxy { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionProxy.class); + + private ConnectionContext context = new ConnectionContext(); + + private static final int REPORT_RETRY_COUNT = ConfigurationFactory.getInstance().getInt( + ConfigurationKeys.CLIENT_REPORT_RETRY_COUNT, DEFAULT_CLIENT_REPORT_RETRY_COUNT); + + public static final boolean IS_REPORT_SUCCESS_ENABLE = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.CLIENT_REPORT_SUCCESS_ENABLE, DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE); + + private final static LockRetryPolicy LOCK_RETRY_POLICY = new LockRetryPolicy(); + + /** + * Instantiates a new Connection proxy. + * + * @param dataSourceProxy the data source proxy + * @param targetConnection the target connection + */ + public ConnectionProxy(DataSourceProxy dataSourceProxy, Connection targetConnection) { + super(dataSourceProxy, targetConnection); + } + + /** + * Gets context. + * + * @return the context + */ + public ConnectionContext getContext() { + return context; + } + + /** + * Bind. + * + * @param xid the xid + */ + public void bind(String xid) { + context.bind(xid); + } + + /** + * set global lock requires flag + * + * @param isLock whether to lock + */ + public void setGlobalLockRequire(boolean isLock) { + context.setGlobalLockRequire(isLock); + } + + /** + * get global lock requires flag + * + * @return the boolean + */ + public boolean isGlobalLockRequire() { + return context.isGlobalLockRequire(); + } + + /** + * Check lock. + * + * @param lockKeys the lockKeys + * @throws SQLException the sql exception + */ + public void checkLock(String lockKeys) throws SQLException { + if (StringUtils.isBlank(lockKeys)) { + return; + } + // Just check lock without requiring lock by now. + try { + boolean lockable = DefaultResourceManager.get().lockQuery(BranchType.AT, + getDataSourceProxy().getResourceId(), context.getXid(), lockKeys); + if (!lockable) { + throw new LockConflictException(); + } + } catch (TransactionException e) { + recognizeLockKeyConflictException(e, lockKeys); + } + } + + /** + * Lock query. + * + * @param lockKeys the lock keys + * @return the boolean + * @throws SQLException the sql exception + */ + public boolean lockQuery(String lockKeys) throws SQLException { + // Just check lock without requiring lock by now. + boolean result = false; + try { + result = DefaultResourceManager.get().lockQuery(BranchType.AT, getDataSourceProxy().getResourceId(), + context.getXid(), lockKeys); + } catch (TransactionException e) { + recognizeLockKeyConflictException(e, lockKeys); + } + return result; + } + + private void recognizeLockKeyConflictException(TransactionException te) throws SQLException { + recognizeLockKeyConflictException(te, null); + } + + private void recognizeLockKeyConflictException(TransactionException te, String lockKeys) throws SQLException { + if (te.getCode() == TransactionExceptionCode.LockKeyConflict) { + StringBuilder reasonBuilder = new StringBuilder("get global lock fail, xid:"); + reasonBuilder.append(context.getXid()); + if (StringUtils.isNotBlank(lockKeys)) { + reasonBuilder.append(", lockKeys:").append(lockKeys); + } + throw new LockConflictException(reasonBuilder.toString()); + } else { + throw new SQLException(te); + } + + } + + /** + * append sqlUndoLog + * + * @param sqlUndoLog the sql undo log + */ + public void appendUndoLog(SQLUndoLog sqlUndoLog) { + context.appendUndoItem(sqlUndoLog); + } + + /** + * append lockKey + * + * @param lockKey the lock key + */ + public void appendLockKey(String lockKey) { + context.appendLockKey(lockKey); + } + + @Override + public void commit() throws SQLException { + try { + LOCK_RETRY_POLICY.execute(() -> { + doCommit(); + return null; + }); + } catch (SQLException e) { + if (targetConnection != null && !getAutoCommit() && !getContext().isAutoCommitChanged()) { + rollback(); + } + throw e; + } catch (Exception e) { + throw new SQLException(e); + } + } + + @Override + public Savepoint setSavepoint() throws SQLException { + Savepoint savepoint = targetConnection.setSavepoint(); + context.appendSavepoint(savepoint); + return savepoint; + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + Savepoint savepoint = targetConnection.setSavepoint(name); + context.appendSavepoint(savepoint); + return savepoint; + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + targetConnection.rollback(savepoint); + context.removeSavepoint(savepoint); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + targetConnection.releaseSavepoint(savepoint); + context.releaseSavepoint(savepoint); + } + + + private void doCommit() throws SQLException { + if (context.inGlobalTransaction()) { + processGlobalTransactionCommit(); + } else if (context.isGlobalLockRequire()) { + processLocalCommitWithGlobalLocks(); + } else { + targetConnection.commit(); + } + } + + private void processLocalCommitWithGlobalLocks() throws SQLException { + checkLock(context.buildLockKeys()); + try { + targetConnection.commit(); + } catch (Throwable ex) { + throw new SQLException(ex); + } + context.reset(); + } + + private void processGlobalTransactionCommit() throws SQLException { + try { + register(); + } catch (TransactionException e) { + recognizeLockKeyConflictException(e, context.buildLockKeys()); + } + try { + UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this); + targetConnection.commit(); + } catch (Throwable ex) { + LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex); + report(false); + throw new SQLException(ex); + } + if (IS_REPORT_SUCCESS_ENABLE) { + report(true); + } + context.reset(); + } + + private void register() throws TransactionException { + if (!context.hasUndoLog() || !context.hasLockKey()) { + return; + } + Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), + null, context.getXid(), null, context.buildLockKeys()); + context.setBranchId(branchId); + } + + @Override + public void rollback() throws SQLException { + targetConnection.rollback(); + if (context.inGlobalTransaction() && context.isBranchRegistered()) { + report(false); + } + context.reset(); + } + + /** + * change connection autoCommit to false by seata + * + * @throws SQLException the sql exception + */ + public void changeAutoCommit() throws SQLException { + getContext().setAutoCommitChanged(true); + setAutoCommit(false); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + if ((context.inGlobalTransaction() || context.isGlobalLockRequire()) && autoCommit && !getAutoCommit()) { + // change autocommit from false to true, we should commit() first according to JDBC spec. + doCommit(); + } + targetConnection.setAutoCommit(autoCommit); + } + + private void report(boolean commitDone) throws SQLException { + if (context.getBranchId() == null) { + return; + } + int retry = REPORT_RETRY_COUNT; + while (retry > 0) { + try { + DefaultResourceManager.get().branchReport(BranchType.AT, context.getXid(), context.getBranchId(), + commitDone ? BranchStatus.PhaseOne_Done : BranchStatus.PhaseOne_Failed, null); + return; + } catch (Throwable ex) { + LOGGER.error("Failed to report [" + context.getBranchId() + "/" + context.getXid() + "] commit done [" + + commitDone + "] Retry Countdown: " + retry); + retry--; + + if (retry == 0) { + throw new SQLException("Failed to report branch status " + commitDone, ex); + } + } + } + } + + public static class LockRetryPolicy { + protected static final boolean LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT = ConfigurationFactory + .getInstance().getBoolean(ConfigurationKeys.CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT, DEFAULT_CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT); + + public T execute(Callable callable) throws Exception { + if (LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT) { + return callable.call(); + } else { + return doRetryOnLockConflict(callable); + } + } + + protected T doRetryOnLockConflict(Callable callable) throws Exception { + LockRetryController lockRetryController = new LockRetryController(); + while (true) { + try { + return callable.call(); + } catch (LockConflictException lockConflict) { + onException(lockConflict); + lockRetryController.sleep(lockConflict); + } catch (Exception e) { + onException(e); + throw e; + } + } + } + + /** + * Callback on exception in doLockRetryOnConflict. + * + * @param e invocation exception + * @throws Exception error + */ + protected void onException(Exception e) throws Exception { + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/DataCompareUtils.java b/rm-datasource/src/main/java/io/seata/rm/datasource/DataCompareUtils.java new file mode 100644 index 0000000..8af8e20 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/DataCompareUtils.java @@ -0,0 +1,220 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.core.model.Result; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoLogManager; +import io.seata.rm.datasource.undo.parser.FastjsonUndoLogParser; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.HashMap; +import java.util.Comparator; +import java.util.stream.Collectors; + +/** + * The type Data compare utils. + * + * @author Geng Zhang + */ +public class DataCompareUtils { + + private DataCompareUtils() { + + } + + /** + * Is field equals result. + * + * @param f0 the f 0 + * @param f1 the f 1 + * @return the result + */ + public static Result isFieldEquals(Field f0, Field f1) { + if (f0 == null) { + return Result.build(f1 == null); + } else { + if (f1 == null) { + return Result.build(false); + } else { + if (StringUtils.equalsIgnoreCase(f0.getName(), f1.getName()) + && f0.getType() == f1.getType()) { + if (f0.getValue() == null) { + return Result.build(f1.getValue() == null); + } else { + if (f1.getValue() == null) { + return Result.buildWithParams(false, "Field not equals, name {}, new value is null", f0.getName()); + } else { + String currentSerializer = AbstractUndoLogManager.getCurrentSerializer(); + if (StringUtils.equals(currentSerializer, FastjsonUndoLogParser.NAME)) { + convertType(f0, f1); + } + boolean result = Objects.deepEquals(f0.getValue(), f1.getValue()); + if (result) { + return Result.ok(); + } else { + return Result.buildWithParams(false, "Field not equals, name {}, old value {}, new value {}", f0.getName(), f0.getValue(), f1.getValue()); + } + } + } + } else { + return Result.buildWithParams(false, "Field not equals, old name {} type {}, new name {} type {}", f0.getName(), f0.getType(), f1.getName(), f1.getType()); + } + } + } + } + + private static void convertType(Field f0, Field f1) { + int f0Type = f0.getType(); + int f1Type = f1.getType(); + if (f0Type == Types.TIMESTAMP && f0.getValue().getClass().equals(String.class)) { + f0.setValue(Timestamp.valueOf(f0.getValue().toString())); + } + if (f1Type == Types.TIMESTAMP && f1.getValue().getClass().equals(String.class)) { + f1.setValue(Timestamp.valueOf(f1.getValue().toString())); + } + if (f0Type == Types.DECIMAL && f0.getValue().getClass().equals(Integer.class)) { + f0.setValue(new BigDecimal(f0.getValue().toString())); + } + if (f1Type == Types.DECIMAL && f1.getValue().getClass().equals(Integer.class)) { + f1.setValue(new BigDecimal(f1.getValue().toString())); + } + if (f0Type == Types.BIGINT && f0.getValue().getClass().equals(Integer.class)) { + f0.setValue(Long.parseLong(f0.getValue().toString())); + } + if (f1Type == Types.BIGINT && f1.getValue().getClass().equals(Integer.class)) { + f1.setValue(Long.parseLong(f1.getValue().toString())); + } + } + + /** + * Is records equals result. + * + * @param beforeImage the before image + * @param afterImage the after image + * @return the result + */ + public static Result isRecordsEquals(TableRecords beforeImage, TableRecords afterImage) { + if (beforeImage == null) { + return Result.build(afterImage == null, null); + } else { + if (afterImage == null) { + return Result.build(false, null); + } + if (beforeImage.getTableName().equalsIgnoreCase(afterImage.getTableName()) + && CollectionUtils.isSizeEquals(beforeImage.getRows(), afterImage.getRows())) { + //when image is EmptyTableRecords, getTableMeta will throw an exception + if (CollectionUtils.isEmpty(beforeImage.getRows())) { + return Result.ok(); + } + return compareRows(beforeImage.getTableMeta(), beforeImage.getRows(), afterImage.getRows()); + } else { + return Result.build(false, null); + } + } + } + + /** + * Is rows equals result. + * + * @param tableMetaData the table meta data + * @param oldRows the old rows + * @param newRows the new rows + * @return the result + */ + public static Result isRowsEquals(TableMeta tableMetaData, List oldRows, List newRows) { + if (!CollectionUtils.isSizeEquals(oldRows, newRows)) { + return Result.build(false, null); + } + return compareRows(tableMetaData, oldRows, newRows); + } + + private static Result compareRows(TableMeta tableMetaData, List oldRows, List newRows) { + // old row to map + Map> oldRowsMap = rowListToMap(oldRows, tableMetaData.getPrimaryKeyOnlyName()); + // new row to map + Map> newRowsMap = rowListToMap(newRows, tableMetaData.getPrimaryKeyOnlyName()); + // compare data + for (Map.Entry> oldEntry : oldRowsMap.entrySet()) { + String key = oldEntry.getKey(); + Map oldRow = oldEntry.getValue(); + Map newRow = newRowsMap.get(key); + if (newRow == null) { + return Result.buildWithParams(false, "compare row failed, rowKey {}, reason [newRow is null]", key); + } + for (Map.Entry oldRowEntry : oldRow.entrySet()) { + String fieldName = oldRowEntry.getKey(); + Field oldField = oldRowEntry.getValue(); + Field newField = newRow.get(fieldName); + if (newField == null) { + return Result.buildWithParams(false, "compare row failed, rowKey {}, fieldName {}, reason [newField is null]", key, fieldName); + } + Result oldEqualsNewFieldResult = isFieldEquals(oldField, newField); + if (!oldEqualsNewFieldResult.getResult()) { + return oldEqualsNewFieldResult; + } + } + } + return Result.ok(); + } + + /** + * Row list to map map. + * + * @param rowList the row list + * @param primaryKeyList the primary key list + * @return the map + */ + public static Map> rowListToMap(List rowList, List primaryKeyList) { + // {value of primaryKey, value of all columns} + Map> rowMap = new HashMap<>(); + for (Row row : rowList) { + //ensure the order of column + List rowFieldList = row.getFields().stream() + .sorted(Comparator.comparing(Field::getName)) + .collect(Collectors.toList()); + // {uppercase fieldName : field} + Map colsMap = new HashMap<>(); + StringBuilder rowKey = new StringBuilder(); + boolean firstUnderline = false; + for (int j = 0; j < rowFieldList.size(); j++) { + Field field = rowFieldList.get(j); + if (primaryKeyList.stream().anyMatch(e -> field.getName().equals(e))) { + if (firstUnderline && j > 0) { + rowKey.append("_"); + } + rowKey.append(String.valueOf(field.getValue())); + firstUnderline = true; + } + colsMap.put(field.getName().trim().toUpperCase(), field); + } + rowMap.put(rowKey.toString(), colsMap); + } + return rowMap; + } + + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceManager.java new file mode 100644 index 0000000..90fca38 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceManager.java @@ -0,0 +1,147 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.core.context.RootContext; +import io.seata.core.exception.RmTransactionException; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.logger.StackTraceLogger; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.GlobalLockQueryRequest; +import io.seata.core.protocol.transaction.GlobalLockQueryResponse; +import io.seata.core.rpc.netty.RmNettyRemotingClient; +import io.seata.rm.AbstractResourceManager; +import io.seata.rm.datasource.undo.UndoLogManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Data source manager. + * + * @author sharajava + */ +public class DataSourceManager extends AbstractResourceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceManager.class); + + private final AsyncWorker asyncWorker = new AsyncWorker(this); + + private final Map dataSourceCache = new ConcurrentHashMap<>(); + + @Override + public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys) throws TransactionException { + GlobalLockQueryRequest request = new GlobalLockQueryRequest(); + request.setXid(xid); + request.setLockKey(lockKeys); + request.setResourceId(resourceId); + try { + GlobalLockQueryResponse response; + if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) { + response = (GlobalLockQueryResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request); + } else { + throw new RuntimeException("unknow situation!"); + } + + if (response.getResultCode() == ResultCode.Failed) { + throw new TransactionException(response.getTransactionExceptionCode(), + "Response[" + response.getMsg() + "]"); + } + return response.isLockable(); + } catch (TimeoutException toe) { + throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe); + } catch (RuntimeException rex) { + throw new RmTransactionException(TransactionExceptionCode.LockableCheckFailed, "Runtime", rex); + } + } + + /** + * Instantiates a new Data source manager. + */ + public DataSourceManager() { + } + + @Override + public void registerResource(Resource resource) { + DataSourceProxy dataSourceProxy = (DataSourceProxy) resource; + dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy); + super.registerResource(dataSourceProxy); + } + + @Override + public void unregisterResource(Resource resource) { + throw new NotSupportYetException("unregister a resource"); + } + + /** + * Get data source proxy. + * + * @param resourceId the resource id + * @return the data source proxy + */ + public DataSourceProxy get(String resourceId) { + return (DataSourceProxy) dataSourceCache.get(resourceId); + } + + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + return asyncWorker.branchCommit(xid, branchId, resourceId); + } + + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + DataSourceProxy dataSourceProxy = get(resourceId); + if (dataSourceProxy == null) { + throw new ShouldNeverHappenException(); + } + try { + UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).undo(dataSourceProxy, xid, branchId); + } catch (TransactionException te) { + StackTraceLogger.info(LOGGER, te, + "branchRollback failed. branchType:[{}], xid:[{}], branchId:[{}], resourceId:[{}], applicationData:[{}]. reason:[{}]", + new Object[]{branchType, xid, branchId, resourceId, applicationData, te.getMessage()}); + if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) { + return BranchStatus.PhaseTwo_RollbackFailed_Unretryable; + } else { + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } + } + return BranchStatus.PhaseTwo_Rollbacked; + + } + + @Override + public Map getManagedResources() { + return dataSourceCache; + } + + @Override + public BranchType getBranchType() { + return BranchType.AT; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceProxy.java new file mode 100644 index 0000000..f177f4a --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/DataSourceProxy.java @@ -0,0 +1,222 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import javax.sql.DataSource; + +import io.seata.common.thread.NamedThreadFactory; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import io.seata.rm.datasource.util.JdbcUtils; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_TABLE_META_CHECK_ENABLE; +import static io.seata.common.DefaultValues.DEFAULT_TABLE_META_CHECKER_INTERVAL; + +/** + * The type Data source proxy. + * + * @author sharajava + */ +public class DataSourceProxy extends AbstractDataSourceProxy implements Resource { + + private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceProxy.class); + + private static final String DEFAULT_RESOURCE_GROUP_ID = "DEFAULT"; + + private String resourceGroupId; + + private String jdbcUrl; + + private String dbType; + + private String userName; + + /** + * Enable the table meta checker + */ + private static boolean ENABLE_TABLE_META_CHECKER_ENABLE = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.CLIENT_TABLE_META_CHECK_ENABLE, DEFAULT_CLIENT_TABLE_META_CHECK_ENABLE); + + /** + * Table meta checker interval + */ + private static final long TABLE_META_CHECKER_INTERVAL = ConfigurationFactory.getInstance().getLong( + ConfigurationKeys.CLIENT_TABLE_META_CHECKER_INTERVAL, DEFAULT_TABLE_META_CHECKER_INTERVAL); + + private final ScheduledExecutorService tableMetaExcutor = new ScheduledThreadPoolExecutor(1, + new NamedThreadFactory("tableMetaChecker", 1, true)); + + /** + * Instantiates a new Data source proxy. + * + * @param targetDataSource the target data source + */ + public DataSourceProxy(DataSource targetDataSource) { + this(targetDataSource, DEFAULT_RESOURCE_GROUP_ID); + } + + /** + * Instantiates a new Data source proxy. + * + * @param targetDataSource the target data source + * @param resourceGroupId the resource group id + */ + public DataSourceProxy(DataSource targetDataSource, String resourceGroupId) { + if (targetDataSource instanceof SeataDataSourceProxy) { + LOGGER.info("Unwrap the target data source, because the type is: {}", targetDataSource.getClass().getName()); + targetDataSource = ((SeataDataSourceProxy) targetDataSource).getTargetDataSource(); + } + this.targetDataSource = targetDataSource; + init(targetDataSource, resourceGroupId); + } + + private void init(DataSource dataSource, String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + try (Connection connection = dataSource.getConnection()) { + jdbcUrl = connection.getMetaData().getURL(); + dbType = JdbcUtils.getDbType(jdbcUrl); + if (JdbcConstants.ORACLE.equals(dbType)) { + userName = connection.getMetaData().getUserName(); + } + } catch (SQLException e) { + throw new IllegalStateException("can not init dataSource", e); + } + DefaultResourceManager.get().registerResource(this); + if (ENABLE_TABLE_META_CHECKER_ENABLE) { + tableMetaExcutor.scheduleAtFixedRate(() -> { + try (Connection connection = dataSource.getConnection()) { + TableMetaCacheFactory.getTableMetaCache(DataSourceProxy.this.getDbType()) + .refresh(connection, DataSourceProxy.this.getResourceId()); + } catch (Exception ignore) { + } + }, 0, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS); + } + + //Set the default branch type to 'AT' in the RootContext. + RootContext.setDefaultBranchType(this.getBranchType()); + } + + /** + * Gets plain connection. + * + * @return the plain connection + * @throws SQLException the sql exception + */ + public Connection getPlainConnection() throws SQLException { + return targetDataSource.getConnection(); + } + + /** + * Gets db type. + * + * @return the db type + */ + public String getDbType() { + return dbType; + } + + @Override + public ConnectionProxy getConnection() throws SQLException { + Connection targetConnection = targetDataSource.getConnection(); + return new ConnectionProxy(this, targetConnection); + } + + @Override + public ConnectionProxy getConnection(String username, String password) throws SQLException { + Connection targetConnection = targetDataSource.getConnection(username, password); + return new ConnectionProxy(this, targetConnection); + } + + @Override + public String getResourceGroupId() { + return resourceGroupId; + } + + @Override + public String getResourceId() { + if (JdbcConstants.POSTGRESQL.equals(dbType)) { + return getPGResourceId(); + } else if (JdbcConstants.ORACLE.equals(dbType) && userName != null) { + return getDefaultResourceId() + "/" + userName; + } else { + return getDefaultResourceId(); + } + } + + /** + * get the default resource id + * @return resource id + */ + private String getDefaultResourceId() { + if (jdbcUrl.contains("?")) { + return jdbcUrl.substring(0, jdbcUrl.indexOf('?')); + } else { + return jdbcUrl; + } + } + + /** + * prevent pg sql url like + * jdbc:postgresql://127.0.0.1:5432/seata?currentSchema=public + * jdbc:postgresql://127.0.0.1:5432/seata?currentSchema=seata + * cause the duplicated resourceId + * it will cause the problem like + * 1.get file lock fail + * 2.error table meta cache + * @return resourceId + */ + private String getPGResourceId() { + if (jdbcUrl.contains("?")) { + StringBuilder jdbcUrlBuilder = new StringBuilder(); + jdbcUrlBuilder.append(jdbcUrl.substring(0, jdbcUrl.indexOf('?'))); + StringBuilder paramsBuilder = new StringBuilder(); + String paramUrl = jdbcUrl.substring(jdbcUrl.indexOf('?') + 1, jdbcUrl.length()); + String[] urlParams = paramUrl.split("&"); + for (String urlParam : urlParams) { + if (urlParam.contains("currentSchema")) { + paramsBuilder.append(urlParam); + break; + } + } + + if (paramsBuilder.length() > 0) { + jdbcUrlBuilder.append("?"); + jdbcUrlBuilder.append(paramsBuilder); + } + return jdbcUrlBuilder.toString(); + } else { + return jdbcUrl; + } + } + + @Override + public BranchType getBranchType() { + return BranchType.AT; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/PreparedStatementProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/PreparedStatementProxy.java new file mode 100644 index 0000000..1272ca6 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/PreparedStatementProxy.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Map; + +import io.seata.rm.datasource.exec.ExecuteTemplate; +import io.seata.sqlparser.ParametersHolder; + +/** + * The type Prepared statement proxy. + * + * @author sharajava + */ +public class PreparedStatementProxy extends AbstractPreparedStatementProxy + implements PreparedStatement, ParametersHolder { + + @Override + public Map> getParameters() { + return parameters; + } + + /** + * Instantiates a new Prepared statement proxy. + * + * @param connectionProxy the connection proxy + * @param targetStatement the target statement + * @param targetSQL the target sql + * @throws SQLException the sql exception + */ + public PreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement, + String targetSQL) throws SQLException { + super(connectionProxy, targetStatement, targetSQL); + } + + @Override + public boolean execute() throws SQLException { + return ExecuteTemplate.execute(this, (statement, args) -> statement.execute()); + } + + @Override + public ResultSet executeQuery() throws SQLException { + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeQuery()); + } + + @Override + public int executeUpdate() throws SQLException { + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate()); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/SeataDataSourceProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/SeataDataSourceProxy.java new file mode 100644 index 0000000..b4c9e40 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/SeataDataSourceProxy.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import javax.sql.DataSource; + +import io.seata.core.model.BranchType; + +/** + * The interface Seata data source. + * + * @author wang.liang + */ +public interface SeataDataSourceProxy extends DataSource { + + /** + * Gets target data source. + * + * @return the target data source + */ + DataSource getTargetDataSource(); + + /** + * Gets branch type. + * + * @return the branch type + */ + BranchType getBranchType(); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/SqlGenerateUtils.java b/rm-datasource/src/main/java/io/seata/rm/datasource/SqlGenerateUtils.java new file mode 100644 index 0000000..5d7cf98 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/SqlGenerateUtils.java @@ -0,0 +1,137 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import io.seata.rm.datasource.sql.struct.Field; + +/** + * generate sql and set value to sql + * + * @author JerryYin + */ +public class SqlGenerateUtils { + + private static final int MAX_IN_SIZE = 1000; + + private SqlGenerateUtils() { + + } + + public static String buildWhereConditionByPKs(List pkNameList, int rowSize, String dbType) + throws SQLException { + return buildWhereConditionByPKs(pkNameList, rowSize, dbType, MAX_IN_SIZE); + + } + /** + * each pk is a condition.the result will like :" (id,userCode) in ((?,?),(?,?)) or (id,userCode) in ((?,?),(?,?) + * ) or (id,userCode) in ((?,?))" + * Build where condition by pks string. + * + * @param pkNameList pk column name list + * @param rowSize the row size of records + * @param dbType the type of database + * @param maxInSize the max in size + * @return return where condition sql string.the sql can search all related records not just one. + * @throws SQLException the sql exception + */ + public static String buildWhereConditionByPKs(List pkNameList, int rowSize, String dbType, int maxInSize) + throws SQLException { + StringBuilder whereStr = new StringBuilder(); + //we must consider the situation of composite primary key + int batchSize = rowSize % maxInSize == 0 ? rowSize / maxInSize : (rowSize / maxInSize) + 1; + for (int batch = 0; batch < batchSize; batch++) { + if (batch > 0) { + whereStr.append(" or "); + } + whereStr.append("("); + for (int i = 0; i < pkNameList.size(); i++) { + if (i > 0) { + whereStr.append(","); + } + whereStr.append(ColumnUtils.addEscape(pkNameList.get(i), dbType)); + } + whereStr.append(") in ( "); + + int eachSize = (batch == batchSize - 1) ? (rowSize % maxInSize == 0 ? maxInSize : rowSize % maxInSize) + : maxInSize; + for (int i = 0; i < eachSize; i++) { + //each row is a bracket + if (i > 0) { + whereStr.append(","); + } + whereStr.append("("); + for (int x = 0; x < pkNameList.size(); x++) { + if (x > 0) { + whereStr.append(","); + } + whereStr.append("?"); + } + whereStr.append(")"); + } + whereStr.append(" )"); + } + + return whereStr.toString(); + } + + /** + * set parameter for PreparedStatement, this is only used in pk sql. + * + * @param pkRowsList pkRowsList + * @param pkColumnNameList pkColumnNameList + * @param pst preparedStatement + * @throws SQLException SQLException + */ + public static void setParamForPk(List> pkRowsList, List pkColumnNameList, + PreparedStatement pst) throws SQLException { + int paramIndex = 1; + for (int i = 0; i < pkRowsList.size(); i++) { + Map rowData = pkRowsList.get(i); + for (String columnName : pkColumnNameList) { + Field pkField = rowData.get(columnName); + pst.setObject(paramIndex, pkField.getValue(), pkField.getType()); + paramIndex++; + } + } + } + + /** + * each pk is a condition.the result will like :" id =? and userCode =?" + * + * @param pkNameList pkNameList + * @param dbType dbType + * @return return where condition sql string.the sql can just search one related record. + */ + public static String buildWhereConditionByPKs(List pkNameList, String dbType) { + StringBuilder whereStr = new StringBuilder(); + //we must consider the situation of composite primary key + for (int i = 0; i < pkNameList.size(); i++) { + if (i > 0) { + whereStr.append(" and "); + } + String pkName = pkNameList.get(i); + whereStr.append(ColumnUtils.addEscape(pkName, dbType)); + whereStr.append(" = ? "); + } + return whereStr.toString(); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/StatementProxy.java b/rm-datasource/src/main/java/io/seata/rm/datasource/StatementProxy.java new file mode 100644 index 0000000..b306254 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/StatementProxy.java @@ -0,0 +1,131 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.exec.ExecuteTemplate; + +/** + * The type Statement proxy. + * + * @param the type parameter + * @author sharajava + */ +public class StatementProxy extends AbstractStatementProxy { + + /** + * Instantiates a new Statement proxy. + * + * @param connectionWrapper the connection wrapper + * @param targetStatement the target statement + * @param targetSQL the target sql + * @throws SQLException the sql exception + */ + public StatementProxy(AbstractConnectionProxy connectionWrapper, T targetStatement, String targetSQL) + throws SQLException { + super(connectionWrapper, targetStatement, targetSQL); + } + + /** + * Instantiates a new Statement proxy. + * + * @param connectionWrapper the connection wrapper + * @param targetStatement the target statement + * @throws SQLException the sql exception + */ + public StatementProxy(AbstractConnectionProxy connectionWrapper, T targetStatement) throws SQLException { + this(connectionWrapper, targetStatement, null); + } + + @Override + public ConnectionProxy getConnectionProxy() { + return (ConnectionProxy) super.getConnectionProxy(); + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeQuery((String) args[0]), sql); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0]), sql); + } + + @Override + public boolean execute(String sql) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0]), sql); + } + + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0],(int)args[1]), sql,autoGeneratedKeys); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0],(int [])args[1]), sql,columnIndexes); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0],(String[])args[1]), sql,columnNames); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0],(int)args[1]), sql,autoGeneratedKeys); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0],(int[])args[1]), sql,columnIndexes); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + this.targetSQL = sql; + return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0],(String[])args[1]), sql,columnNames); + } + + @Override + public void addBatch(String sql) throws SQLException { + if (StringUtils.isNotBlank(targetSQL)) { + targetSQL += "; " + sql; + } else { + targetSQL = sql; + } + targetStatement.addBatch(sql); + } + + @Override + public int[] executeBatch() throws SQLException { + return ExecuteTemplate.execute(this, (statement, args) -> statement.executeBatch()); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutor.java new file mode 100644 index 0000000..d42d365 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutor.java @@ -0,0 +1,202 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.AbstractConnectionProxy; +import io.seata.rm.datasource.ConnectionContext; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Abstract dml base executor. + * + * @param the type parameter + * @param the type parameter + * @author sharajava + */ +public abstract class AbstractDMLBaseExecutor extends BaseTransactionalExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDMLBaseExecutor.class); + + protected static final String WHERE = " WHERE "; + + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public AbstractDMLBaseExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + /** + * Instantiates a new Base transactional executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizers the multi sql recognizer + */ + public AbstractDMLBaseExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + List sqlRecognizers) { + super(statementProxy, statementCallback, sqlRecognizers); + } + + @Override + public T doExecute(Object... args) throws Throwable { + AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); + if (connectionProxy.getAutoCommit()) { + return executeAutoCommitTrue(args); + } else { + return executeAutoCommitFalse(args); + } + } + + /** + * Execute auto commit false t. + * + * @param args the args + * @return the t + * @throws Exception the exception + */ + protected T executeAutoCommitFalse(Object[] args) throws Exception { + if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) { + throw new NotSupportYetException("multi pk only support mysql!"); + } + TableRecords beforeImage = beforeImage(); + T result = statementCallback.execute(statementProxy.getTargetStatement(), args); + TableRecords afterImage = afterImage(beforeImage); + prepareUndoLog(beforeImage, afterImage); + return result; + } + + private boolean isMultiPk() { + if (null != sqlRecognizer) { + return getTableMeta().getPrimaryKeyOnlyName().size() > 1; + } + if (CollectionUtils.isNotEmpty(sqlRecognizers)) { + List distinctSQLRecognizer = sqlRecognizers.stream().filter( + distinctByKey(t -> t.getTableName())).collect(Collectors.toList()); + for (SQLRecognizer sqlRecognizer : distinctSQLRecognizer) { + if (getTableMeta(sqlRecognizer.getTableName()).getPrimaryKeyOnlyName().size() > 1) { + return true; + } + } + } + return false; + } + + private static Predicate distinctByKey(Function keyExtractor) { + Map map = new HashMap<>(); + return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; + } + + + /** + * Execute auto commit true t. + * + * @param args the args + * @return the t + * @throws Throwable the throwable + */ + protected T executeAutoCommitTrue(Object[] args) throws Throwable { + ConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); + try { + connectionProxy.changeAutoCommit(); + return new LockRetryPolicy(connectionProxy).execute(() -> { + T result = executeAutoCommitFalse(args); + connectionProxy.commit(); + return result; + }); + } catch (Exception e) { + // when exception occur in finally,this exception will lost, so just print it here + LOGGER.error("execute executeAutoCommitTrue error:{}", e.getMessage(), e); + if (!LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) { + connectionProxy.getTargetConnection().rollback(); + } + throw e; + } finally { + connectionProxy.getContext().reset(); + connectionProxy.setAutoCommit(true); + } + } + + /** + * Before image table records. + * + * @return the table records + * @throws SQLException the sql exception + */ + protected abstract TableRecords beforeImage() throws SQLException; + + /** + * After image table records. + * + * @param beforeImage the before image + * @return the table records + * @throws SQLException the sql exception + */ + protected abstract TableRecords afterImage(TableRecords beforeImage) throws SQLException; + + private static class LockRetryPolicy extends ConnectionProxy.LockRetryPolicy { + private final ConnectionProxy connection; + + LockRetryPolicy(final ConnectionProxy connection) { + this.connection = connection; + } + + @Override + public T execute(Callable callable) throws Exception { + if (LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT) { + return doRetryOnLockConflict(callable); + } else { + return callable.call(); + } + } + + @Override + protected void onException(Exception e) throws Exception { + ConnectionContext context = connection.getContext(); + //UndoItems can't use the Set collection class to prevent ABA + context.removeSavepoint(null); + connection.getTargetConnection().rollback(); + } + + public static boolean isLockRetryPolicyBranchRollbackOnConflict() { + return LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT; + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseInsertExecutor.java new file mode 100644 index 0000000..49947a8 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseInsertExecutor.java @@ -0,0 +1,413 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Objects; + +import com.google.common.collect.Lists; +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.struct.Sequenceable; +import io.seata.sqlparser.struct.SqlDefaultExpr; +import io.seata.sqlparser.struct.SqlMethodExpr; +import io.seata.sqlparser.struct.SqlSequenceExpr; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Base Insert Executor. + * @author jsbxyyx + */ +public abstract class BaseInsertExecutor extends AbstractDMLBaseExecutor implements InsertExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseInsertExecutor.class); + + protected static final String PLACEHOLDER = "?"; + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public BaseInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + protected TableRecords beforeImage() throws SQLException { + return TableRecords.empty(getTableMeta()); + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + Map> pkValues = getPkValues(); + TableRecords afterImage = buildTableRecords(pkValues); + if (afterImage == null) { + throw new SQLException("Failed to build after-image for insert"); + } + return afterImage; + } + + protected boolean containsPK() { + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizer; + List insertColumns = recognizer.getInsertColumns(); + if (CollectionUtils.isEmpty(insertColumns)) { + return false; + } + return containsPK(insertColumns); + } + + /** + * judge sql specify column + * @return true: contains column. false: not contains column. + */ + protected boolean containsColumns() { + return !((SQLInsertRecognizer) sqlRecognizer).insertColumnsIsEmpty(); + } + + /** + * get pk index + * @return the key is pk column name and the value is index of the pk column + */ + protected Map getPkIndex() { + Map pkIndexMap = new HashMap<>(); + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizer; + List insertColumns = recognizer.getInsertColumns(); + if (CollectionUtils.isNotEmpty(insertColumns)) { + final int insertColumnsSize = insertColumns.size(); + for (int paramIdx = 0; paramIdx < insertColumnsSize; paramIdx++) { + String sqlColumnName = insertColumns.get(paramIdx); + if (containPK(sqlColumnName)) { + pkIndexMap.put(getStandardPkColumnName(sqlColumnName), paramIdx); + } + } + return pkIndexMap; + } + int pkIndex = -1; + Map allColumns = getTableMeta().getAllColumns(); + for (Map.Entry entry : allColumns.entrySet()) { + pkIndex++; + if (containPK(entry.getValue().getColumnName())) { + pkIndexMap.put(ColumnUtils.delEscape(entry.getValue().getColumnName(), getDbType()), pkIndex); + } + } + return pkIndexMap; + } + + + /** + * parse primary key value from statement. + * @return + */ + protected Map> parsePkValuesFromStatement() { + // insert values including PK + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizer; + final Map pkIndexMap = getPkIndex(); + if (pkIndexMap.isEmpty()) { + throw new ShouldNeverHappenException("pkIndex is not found"); + } + Map> pkValuesMap = new HashMap<>(); + boolean ps = true; + if (statementProxy instanceof PreparedStatementProxy) { + PreparedStatementProxy preparedStatementProxy = (PreparedStatementProxy) statementProxy; + + List> insertRows = recognizer.getInsertRows(pkIndexMap.values()); + if (insertRows != null && !insertRows.isEmpty()) { + Map> parameters = preparedStatementProxy.getParameters(); + final int rowSize = insertRows.size(); + int totalPlaceholderNum = -1; + for (List row : insertRows) { + // oracle insert sql statement specify RETURN_GENERATED_KEYS will append :rowid on sql end + // insert parameter count will than the actual +1 + if (row.isEmpty()) { + continue; + } + int currentRowPlaceholderNum = -1; + for (Object r : row) { + if (PLACEHOLDER.equals(r)) { + totalPlaceholderNum += 1; + currentRowPlaceholderNum += 1; + } + } + String pkKey; + int pkIndex; + List pkValues; + for (Map.Entry entry : pkIndexMap.entrySet()) { + pkKey = entry.getKey(); + pkValues = pkValuesMap.get(pkKey); + if (Objects.isNull(pkValues)) { + pkValues = new ArrayList<>(rowSize); + } + pkIndex = entry.getValue(); + Object pkValue = row.get(pkIndex); + if (PLACEHOLDER.equals(pkValue)) { + int currentRowNotPlaceholderNumBeforePkIndex = 0; + for (int n = 0, len = row.size(); n < len; n++) { + Object r = row.get(n); + if (n < pkIndex && !PLACEHOLDER.equals(r)) { + currentRowNotPlaceholderNumBeforePkIndex++; + } + } + int idx = totalPlaceholderNum - currentRowPlaceholderNum + pkIndex - currentRowNotPlaceholderNumBeforePkIndex; + ArrayList parameter = parameters.get(idx + 1); + pkValues.addAll(parameter); + } else { + pkValues.add(pkValue); + } + if (!pkValuesMap.containsKey(ColumnUtils.delEscape(pkKey, getDbType()))) { + pkValuesMap.put(ColumnUtils.delEscape(pkKey, getDbType()), pkValues); + } + } + } + } + } else { + ps = false; + List> insertRows = recognizer.getInsertRows(pkIndexMap.values()); + for (List row : insertRows) { + pkIndexMap.forEach((pkKey, pkIndex) -> { + List pkValues = pkValuesMap.get(pkKey); + if (Objects.isNull(pkValues)) { + pkValuesMap.put(ColumnUtils.delEscape(pkKey, getDbType()), Lists.newArrayList(row.get(pkIndex))); + } else { + pkValues.add(row.get(pkIndex)); + } + }); + } + } + if (pkValuesMap.isEmpty()) { + throw new ShouldNeverHappenException(); + } + boolean b = this.checkPkValues(pkValuesMap, ps); + if (!b) { + throw new NotSupportYetException(String.format("not support sql [%s]", sqlRecognizer.getOriginalSQL())); + } + return pkValuesMap; + } + + /** + * default get generated keys. + * @return + * @throws SQLException + */ + public List getGeneratedKeys() throws SQLException { + // PK is just auto generated + ResultSet genKeys = statementProxy.getGeneratedKeys(); + List pkValues = new ArrayList<>(); + while (genKeys.next()) { + Object v = genKeys.getObject(1); + pkValues.add(v); + } + if (pkValues.isEmpty()) { + throw new NotSupportYetException(String.format("not support sql [%s]", sqlRecognizer.getOriginalSQL())); + } + try { + genKeys.beforeFirst(); + } catch (SQLException e) { + LOGGER.warn("Fail to reset ResultSet cursor. can not get primary key value"); + } + return pkValues; + } + + /** + * the modify for test + * + * @param expr the expr + * @return the pk values by sequence + * @throws SQLException the sql exception + */ + protected List getPkValuesBySequence(SqlSequenceExpr expr) throws SQLException { + List pkValues = null; + try { + pkValues = getGeneratedKeys(); + } catch (NotSupportYetException | SQLException ignore) { + } + + if (!CollectionUtils.isEmpty(pkValues)) { + return pkValues; + } + + Sequenceable sequenceable = (Sequenceable) this; + final String sql = sequenceable.getSequenceSql(expr); + LOGGER.warn("Fail to get auto-generated keys, use '{}' instead. Be cautious, statement could be polluted. Recommend you set the statement to return generated keys.", sql); + + ResultSet genKeys; + genKeys = statementProxy.getConnection().createStatement().executeQuery(sql); + pkValues = new ArrayList<>(); + while (genKeys.next()) { + Object v = genKeys.getObject(1); + pkValues.add(v); + } + return pkValues; + } + + /** + * check pk values for multi Pk + * At most one null per row. + * Method is not allowed. + * + * @param pkValues the pk values + * @return boolean + */ + protected boolean checkPkValuesForMultiPk(Map> pkValues) { + Set pkNames = pkValues.keySet(); + if (pkNames.isEmpty()) { + throw new ShouldNeverHappenException(); + } + int rowSize = pkValues.get(pkNames.iterator().next()).size(); + for (int i = 0; i < rowSize; i++) { + int n = 0; + int m = 0; + for (String name : pkNames) { + Object pkValue = pkValues.get(name).get(i); + if (pkValue instanceof Null) { + n++; + } + if (pkValue instanceof SqlMethodExpr) { + m++; + } + } + if (n > 1) { + return false; + } + if (m > 0) { + return false; + } + } + return true; + } + + /** + * Check pk values boolean. + * + * @param pkValues the pk values + * @param ps the ps + * @return the boolean + */ + protected boolean checkPkValues(Map> pkValues, boolean ps) { + Set pkNames = pkValues.keySet(); + if (pkNames.size() == 1) { + return checkPkValuesForSinglePk(pkValues.get(pkNames.iterator().next()), ps); + } else { + return checkPkValuesForMultiPk(pkValues); + } + } + + /** + * check pk values for single pk + * @param pkValues pkValues + * @param ps true: is prepared statement. false: normal statement. + * @return true: support. false: not support. + */ + protected boolean checkPkValuesForSinglePk(List pkValues, boolean ps) { + /* + ps = true + ----------------------------------------------- + one more + null O O + value O O + method O O + sequence O O + default O O + ----------------------------------------------- + ps = false + ----------------------------------------------- + one more + null O X + value O O + method X X + sequence O X + default O X + ----------------------------------------------- + */ + int n = 0, v = 0, m = 0, s = 0, d = 0; + for (Object pkValue : pkValues) { + if (pkValue instanceof Null) { + n++; + continue; + } + if (pkValue instanceof SqlMethodExpr) { + m++; + continue; + } + if (pkValue instanceof SqlSequenceExpr) { + s++; + continue; + } + if (pkValue instanceof SqlDefaultExpr) { + d++; + continue; + } + v++; + } + + if (!ps) { + if (m > 0) { + return false; + } + if (n == 1 && v == 0 && m == 0 && s == 0 && d == 0) { + return true; + } + if (n == 0 && v > 0 && m == 0 && s == 0 && d == 0) { + return true; + } + if (n == 0 && v == 0 && m == 0 && s == 1 && d == 0) { + return true; + } + if (n == 0 && v == 0 && m == 0 && s == 0 && d == 1) { + return true; + } + return false; + } + + if (n > 0 && v == 0 && m == 0 && s == 0 && d == 0) { + return true; + } + if (n == 0 && v > 0 && m == 0 && s == 0 && d == 0) { + return true; + } + if (n == 0 && v == 0 && m > 0 && s == 0 && d == 0) { + return true; + } + if (n == 0 && v == 0 && m == 0 && s > 0 && d == 0) { + return true; + } + if (n == 0 && v == 0 && m == 0 && s == 0 && d > 0) { + return true; + } + return false; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseTransactionalExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseTransactionalExecutor.java new file mode 100644 index 0000000..1109bb9 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/BaseTransactionalExecutor.java @@ -0,0 +1,417 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.IOUtil; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.WhereRecognizer; + +/** + * The type Base transactional executor. + * + * @param the type parameter + * @param the type parameter + * @author sharajava + */ +public abstract class BaseTransactionalExecutor implements Executor { + + /** + * The Statement proxy. + */ + protected StatementProxy statementProxy; + + /** + * The Statement callback. + */ + protected StatementCallback statementCallback; + + /** + * The Sql recognizer. + */ + protected SQLRecognizer sqlRecognizer; + + /** + * The Sql recognizer. + */ + protected List sqlRecognizers; + + private TableMeta tableMeta; + + /** + * Instantiates a new Base transactional executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public BaseTransactionalExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + this.statementProxy = statementProxy; + this.statementCallback = statementCallback; + this.sqlRecognizer = sqlRecognizer; + } + + /** + * Instantiates a new Base transactional executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizers the multi sql recognizer + */ + public BaseTransactionalExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + List sqlRecognizers) { + this.statementProxy = statementProxy; + this.statementCallback = statementCallback; + this.sqlRecognizers = sqlRecognizers; + } + + @Override + public T execute(Object... args) throws Throwable { + String xid = RootContext.getXID(); + if (xid != null) { + statementProxy.getConnectionProxy().bind(xid); + } + + statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock()); + return doExecute(args); + } + + /** + * Do execute object. + * + * @param args the args + * @return the object + * @throws Throwable the throwable + */ + protected abstract T doExecute(Object... args) throws Throwable; + + + /** + * build buildWhereCondition + * + * @param recognizer the recognizer + * @param paramAppenderList the param paramAppender list + * @return the string + */ + protected String buildWhereCondition(WhereRecognizer recognizer, ArrayList> paramAppenderList) { + String whereCondition = null; + if (statementProxy instanceof ParametersHolder) { + whereCondition = recognizer.getWhereCondition((ParametersHolder) statementProxy, paramAppenderList); + } else { + whereCondition = recognizer.getWhereCondition(); + } + //process batch operation + if (StringUtils.isNotBlank(whereCondition) && CollectionUtils.isNotEmpty(paramAppenderList) && paramAppenderList.size() > 1) { + StringBuilder whereConditionSb = new StringBuilder(); + whereConditionSb.append(" ( ").append(whereCondition).append(" ) "); + for (int i = 1; i < paramAppenderList.size(); i++) { + whereConditionSb.append(" or ( ").append(whereCondition).append(" ) "); + } + whereCondition = whereConditionSb.toString(); + } + return whereCondition; + } + + /** + * Gets column name in sql. + * + * @param columnName the column name + * @return the column name in sql + */ + protected String getColumnNameInSQL(String columnName) { + String tableAlias = sqlRecognizer.getTableAlias(); + return tableAlias == null ? columnName : tableAlias + "." + columnName; + } + + /** + * Gets several column name in sql. + * + * @param columnNameList the column name + * @return the column name in sql + */ + protected String getColumnNamesInSQL(List columnNameList) { + if (Objects.isNull(columnNameList) || columnNameList.isEmpty()) { + return null; + } + StringBuilder columnNamesStr = new StringBuilder(); + for (int i = 0; i < columnNameList.size(); i++) { + if (i > 0) { + columnNamesStr.append(" , "); + } + columnNamesStr.append(getColumnNameInSQL(columnNameList.get(i))); + } + return columnNamesStr.toString(); + } + + /** + * Gets from table in sql. + * + * @return the from table in sql + */ + protected String getFromTableInSQL() { + String tableName = sqlRecognizer.getTableName(); + String tableAlias = sqlRecognizer.getTableAlias(); + return tableAlias == null ? tableName : tableName + " " + tableAlias; + } + + /** + * Gets table meta. + * + * @return the table meta + */ + protected TableMeta getTableMeta() { + return getTableMeta(sqlRecognizer.getTableName()); + } + + /** + * Gets table meta. + * + * @param tableName the table name + * @return the table meta + */ + protected TableMeta getTableMeta(String tableName) { + if (tableMeta != null) { + return tableMeta; + } + ConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); + tableMeta = TableMetaCacheFactory.getTableMetaCache(connectionProxy.getDbType()) + .getTableMeta(connectionProxy.getTargetConnection(), tableName, connectionProxy.getDataSourceProxy().getResourceId()); + return tableMeta; + } + + /** + * the columns contains table meta pk + * + * @param columns the column name list + * @return true: contains pk false: not contains pk + */ + protected boolean containsPK(List columns) { + if (columns == null || columns.isEmpty()) { + return false; + } + List newColumns = ColumnUtils.delEscape(columns, getDbType()); + return getTableMeta().containsPK(newColumns); + } + + + /** + * compare column name and primary key name + * + * @param columnName the primary key column name + * @return true: contain false: not contain + */ + protected boolean containPK(String columnName) { + String newColumnName = ColumnUtils.delEscape(columnName, getDbType()); + return CollectionUtils.toUpperList(getTableMeta().getPrimaryKeyOnlyName()).contains(newColumnName.toUpperCase()); + } + + /** + * get standard pk column name from user sql column name + * + * @param userColumnName the user column name + * @return standard pk column name + */ + protected String getStandardPkColumnName(String userColumnName) { + String newUserColumnName = ColumnUtils.delEscape(userColumnName, getDbType()); + for (String cn : getTableMeta().getPrimaryKeyOnlyName()) { + if (cn.toUpperCase().equals(newUserColumnName.toUpperCase())) { + return cn; + } + } + return null; + } + + /** + * prepare undo log. + * + * @param beforeImage the before image + * @param afterImage the after image + * @throws SQLException the sql exception + */ + protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException { + if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) { + return; + } + if (SQLType.UPDATE == sqlRecognizer.getSQLType()) { + if (beforeImage.getRows().size() != afterImage.getRows().size()) { + throw new ShouldNeverHappenException("Before image size is not equaled to after image size, probably because you updated the primary keys."); + } + } + ConnectionProxy connectionProxy = statementProxy.getConnectionProxy(); + + TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage; + String lockKeys = buildLockKey(lockKeyRecords); + if (null != lockKeys) { + connectionProxy.appendLockKey(lockKeys); + + SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage); + connectionProxy.appendUndoLog(sqlUndoLog); + } + } + + /** + * build lockKey + * + * @param rowsIncludingPK the records + * @return the string as local key. the local key example(multi pk): "t_user:1_a,2_b" + */ + protected String buildLockKey(TableRecords rowsIncludingPK) { + if (rowsIncludingPK.size() == 0) { + return null; + } + + StringBuilder sb = new StringBuilder(); + sb.append(rowsIncludingPK.getTableMeta().getTableName()); + sb.append(":"); + int filedSequence = 0; + List> pksRows = rowsIncludingPK.pkRows(); + for (Map rowMap : pksRows) { + int pkSplitIndex = 0; + for (String pkName : getTableMeta().getPrimaryKeyOnlyName()) { + if (pkSplitIndex > 0) { + sb.append("_"); + } + sb.append(rowMap.get(pkName).getValue()); + pkSplitIndex++; + } + filedSequence++; + if (filedSequence < pksRows.size()) { + sb.append(","); + } + } + return sb.toString(); + } + + /** + * build a SQLUndoLog + * + * @param beforeImage the before image + * @param afterImage the after image + * @return sql undo log + */ + protected SQLUndoLog buildUndoItem(TableRecords beforeImage, TableRecords afterImage) { + SQLType sqlType = sqlRecognizer.getSQLType(); + String tableName = sqlRecognizer.getTableName(); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(sqlType); + sqlUndoLog.setTableName(tableName); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + return sqlUndoLog; + } + + + /** + * build a BeforeImage + * + * @param tableMeta the tableMeta + * @param selectSQL the selectSQL + * @param paramAppenderList the paramAppender list + * @return a tableRecords + * @throws SQLException the sql exception + */ + protected TableRecords buildTableRecords(TableMeta tableMeta, String selectSQL, ArrayList> paramAppenderList) throws SQLException { + ResultSet rs = null; + try (PreparedStatement ps = statementProxy.getConnection().prepareStatement(selectSQL)) { + if (CollectionUtils.isNotEmpty(paramAppenderList)) { + for (int i = 0, ts = paramAppenderList.size(); i < ts; i++) { + List paramAppender = paramAppenderList.get(i); + for (int j = 0, ds = paramAppender.size(); j < ds; j++) { + ps.setObject(i * ds + j + 1, paramAppender.get(j)); + } + } + } + rs = ps.executeQuery(); + return TableRecords.buildRecords(tableMeta, rs); + } finally { + IOUtil.close(rs); + } + } + + /** + * build TableRecords + * + * @param pkValuesMap the pkValuesMap + * @return return TableRecords; + * @throws SQLException the sql exception + */ + protected TableRecords buildTableRecords(Map> pkValuesMap) throws SQLException { + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + StringBuilder sql = new StringBuilder() + .append("SELECT * FROM ") + .append(getFromTableInSQL()) + .append(" WHERE "); + // build check sql + String firstKey = pkValuesMap.keySet().stream().findFirst().get(); + int rowSize = pkValuesMap.get(firstKey).size(); + sql.append(SqlGenerateUtils.buildWhereConditionByPKs(pkColumnNameList, rowSize, getDbType())); + + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = statementProxy.getConnection().prepareStatement(sql.toString()); + + int paramIndex = 1; + for (int r = 0; r < rowSize; r++) { + for (int c = 0; c < pkColumnNameList.size(); c++) { + List pkColumnValueList = pkValuesMap.get(pkColumnNameList.get(c)); + int dataType = tableMeta.getColumnMeta(pkColumnNameList.get(c)).getDataType(); + ps.setObject(paramIndex, pkColumnValueList.get(r), dataType); + paramIndex++; + } + } + rs = ps.executeQuery(); + return TableRecords.buildRecords(getTableMeta(), rs); + } finally { + IOUtil.close(rs); + } + } + + /** + * get db type + * + * @return db type + */ + protected String getDbType() { + return statementProxy.getConnectionProxy().getDbType(); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/DeleteExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/DeleteExecutor.java new file mode 100644 index 0000000..e48e865 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/DeleteExecutor.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLDeleteRecognizer; +import io.seata.sqlparser.SQLRecognizer; + +/** + * The type Delete executor. + * + * @author sharajava + * + * @param the type parameter + * @param the type parameter + */ +public class DeleteExecutor extends AbstractDMLBaseExecutor { + + /** + * Instantiates a new Delete executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public DeleteExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + protected TableRecords beforeImage() throws SQLException { + SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer; + TableMeta tmeta = getTableMeta(visitor.getTableName()); + ArrayList> paramAppenderList = new ArrayList<>(); + String selectSQL = buildBeforeImageSQL(visitor, tmeta, paramAppenderList); + return buildTableRecords(tmeta, selectSQL, paramAppenderList); + } + + private String buildBeforeImageSQL(SQLDeleteRecognizer visitor, TableMeta tableMeta, ArrayList> paramAppenderList) { + String whereCondition = buildWhereCondition(visitor, paramAppenderList); + StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL()); + if (StringUtils.isNotBlank(whereCondition)) { + suffix.append(WHERE).append(whereCondition); + } + String orderBy = visitor.getOrderBy(); + if (StringUtils.isNotBlank(orderBy)) { + suffix.append(orderBy); + } + ParametersHolder parametersHolder = statementProxy instanceof ParametersHolder ? (ParametersHolder)statementProxy : null; + String limit = visitor.getLimit(parametersHolder, paramAppenderList); + if (StringUtils.isNotBlank(limit)) { + suffix.append(limit); + } + suffix.append(" FOR UPDATE"); + StringJoiner selectSQLAppender = new StringJoiner(", ", "SELECT ", suffix.toString()); + for (String column : tableMeta.getAllColumns().keySet()) { + selectSQLAppender.add(getColumnNameInSQL(ColumnUtils.addEscape(column, getDbType()))); + } + return selectSQLAppender.toString(); + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + return TableRecords.empty(getTableMeta()); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/ExecuteTemplate.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/ExecuteTemplate.java new file mode 100644 index 0000000..12bd2a4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/ExecuteTemplate.java @@ -0,0 +1,122 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import io.seata.sqlparser.SQLRecognizer; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; + +/** + * The type Execute template. + * + * @author sharajava + */ +public class ExecuteTemplate { + + /** + * Execute t. + * + * @param the type parameter + * @param the type parameter + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param args the args + * @return the t + * @throws SQLException the sql exception + */ + public static T execute(StatementProxy statementProxy, + StatementCallback statementCallback, + Object... args) throws SQLException { + return execute(null, statementProxy, statementCallback, args); + } + + /** + * Execute t. + * + * @param the type parameter + * @param the type parameter + * @param sqlRecognizers the sql recognizer list + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param args the args + * @return the t + * @throws SQLException the sql exception + */ + public static T execute(List sqlRecognizers, + StatementProxy statementProxy, + StatementCallback statementCallback, + Object... args) throws SQLException { + if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) { + // Just work as original statement + return statementCallback.execute(statementProxy.getTargetStatement(), args); + } + + String dbType = statementProxy.getConnectionProxy().getDbType(); + if (CollectionUtils.isEmpty(sqlRecognizers)) { + sqlRecognizers = SQLVisitorFactory.get( + statementProxy.getTargetSQL(), + dbType); + } + Executor executor; + if (CollectionUtils.isEmpty(sqlRecognizers)) { + executor = new PlainExecutor<>(statementProxy, statementCallback); + } else { + if (sqlRecognizers.size() == 1) { + SQLRecognizer sqlRecognizer = sqlRecognizers.get(0); + switch (sqlRecognizer.getSQLType()) { + case INSERT: + executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType, + new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class}, + new Object[]{statementProxy, statementCallback, sqlRecognizer}); + break; + case UPDATE: + executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); + break; + case DELETE: + executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer); + break; + case SELECT_FOR_UPDATE: + executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer); + break; + default: + executor = new PlainExecutor<>(statementProxy, statementCallback); + break; + } + } else { + executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers); + } + } + T rs; + try { + rs = executor.execute(args); + } catch (Throwable ex) { + if (!(ex instanceof SQLException)) { + // Turn other exception into SQLException + ex = new SQLException(ex); + } + throw (SQLException) ex; + } + return rs; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/Executor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/Executor.java new file mode 100644 index 0000000..c4ef3bb --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/Executor.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +/** + * The interface Executor. + * + * @author sharajava + * + * @param the type parameter + */ +public interface Executor { + + /** + * Execute t. + * + * @param args the args + * @return the t + * @throws Throwable the throwable + */ + T execute(Object... args) throws Throwable; +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/InsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/InsertExecutor.java new file mode 100644 index 0000000..05546e2 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/InsertExecutor.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * The interface Insert executor. + * + * @param the type parameter + * @author jsbxyyx + */ +public interface InsertExecutor extends Executor { + + /** + * get primary key values. + * + * @return The primary key value. + * @throws SQLException the sql exception + */ + Map> getPkValues() throws SQLException; + + /** + * get primary key values by insert column. + * + * @return pk values by column + * @throws SQLException the sql exception + */ + Map> getPkValuesByColumn() throws SQLException; + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockConflictException.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockConflictException.java new file mode 100644 index 0000000..e59fb40 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockConflictException.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; + +/** + * The type Lock conflict exception. + * + * @author sharajava + */ +public class LockConflictException extends SQLException { + + /** + * Instantiates a new Lock conflict exception. + */ + public LockConflictException() { + } + + public LockConflictException(String message) { + super(message); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockRetryController.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockRetryController.java new file mode 100644 index 0000000..a78be36 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockRetryController.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.DefaultValues; +import io.seata.common.util.NumberUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationCache; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.config.ConfigurationChangeListener; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.context.GlobalLockConfigHolder; +import io.seata.core.model.GlobalLockConfig; + +/** + * Lock retry controller + * + * @author sharajava + */ +public class LockRetryController { + + private static final GlobalConfig LISTENER = new GlobalConfig(); + + static { + ConfigurationCache.addConfigListener(ConfigurationKeys.CLIENT_LOCK_RETRY_INTERVAL, LISTENER); + ConfigurationCache.addConfigListener(ConfigurationKeys.CLIENT_LOCK_RETRY_TIMES, LISTENER); + } + + private int lockRetryInternal; + + private int lockRetryTimes; + + /** + * Instantiates a new Lock retry controller. + */ + public LockRetryController() { + this.lockRetryInternal = getLockRetryInternal(); + this.lockRetryTimes = getLockRetryTimes(); + } + + /** + * Sleep. + * + * @param e the e + * @throws LockWaitTimeoutException the lock wait timeout exception + */ + public void sleep(Exception e) throws LockWaitTimeoutException { + if (--lockRetryTimes < 0) { + throw new LockWaitTimeoutException("Global lock wait timeout", e); + } + + try { + Thread.sleep(lockRetryInternal); + } catch (InterruptedException ignore) { + } + } + + int getLockRetryInternal() { + // get customized config first + GlobalLockConfig config = GlobalLockConfigHolder.getCurrentGlobalLockConfig(); + if (config != null) { + int configInternal = config.getLockRetryInternal(); + if (configInternal > 0) { + return configInternal; + } + } + // if there is no customized config, use global config instead + return LISTENER.getGlobalLockRetryInternal(); + } + + int getLockRetryTimes() { + // get customized config first + GlobalLockConfig config = GlobalLockConfigHolder.getCurrentGlobalLockConfig(); + if (config != null) { + int configTimes = config.getLockRetryTimes(); + if (configTimes >= 0) { + return configTimes; + } + } + // if there is no customized config, use global config instead + return LISTENER.getGlobalLockRetryTimes(); + } + + static class GlobalConfig implements ConfigurationChangeListener { + + private volatile int globalLockRetryInternal; + + private volatile int globalLockRetryTimes; + + private final int defaultRetryInternal = DefaultValues.DEFAULT_CLIENT_LOCK_RETRY_INTERVAL; + private final int defaultRetryTimes = DefaultValues.DEFAULT_CLIENT_LOCK_RETRY_TIMES; + + public GlobalConfig() { + Configuration configuration = ConfigurationFactory.getInstance(); + globalLockRetryInternal = configuration.getInt(ConfigurationKeys.CLIENT_LOCK_RETRY_INTERVAL, defaultRetryInternal); + globalLockRetryTimes = configuration.getInt(ConfigurationKeys.CLIENT_LOCK_RETRY_TIMES, defaultRetryTimes); + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + String dataId = event.getDataId(); + String newValue = event.getNewValue(); + if (ConfigurationKeys.CLIENT_LOCK_RETRY_INTERVAL.equals(dataId)) { + globalLockRetryInternal = NumberUtils.toInt(newValue, defaultRetryInternal); + } + if (ConfigurationKeys.CLIENT_LOCK_RETRY_TIMES.equals(dataId)) { + globalLockRetryTimes = NumberUtils.toInt(newValue, defaultRetryTimes); + } + } + + public int getGlobalLockRetryInternal() { + return globalLockRetryInternal; + } + + public int getGlobalLockRetryTimes() { + return globalLockRetryTimes; + } + } +} \ No newline at end of file diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockWaitTimeoutException.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockWaitTimeoutException.java new file mode 100644 index 0000000..a6e8592 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/LockWaitTimeoutException.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; + +/** + * The type Lock wait timeout exception. + * + * @author sharajava + */ +public class LockWaitTimeoutException extends SQLException { + private static final long serialVersionUID = -6754599774015964707L; + + /** + * Instantiates a new Lock wait timeout exception. + */ + public LockWaitTimeoutException() { + } + + /** + * Instantiates a new Lock wait timeout exception. + * + * @param reason the reason + * @param cause the cause + */ + public LockWaitTimeoutException(String reason, Throwable cause) { + super(reason, cause); + } + + /** + * Instantiates a new Lock wait timeout exception. + * + * @param e the e + */ + public LockWaitTimeoutException(Throwable e) { + super(e); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiDeleteExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiDeleteExecutor.java new file mode 100644 index 0000000..5eb2418 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiDeleteExecutor.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.util.StringUtils; + + +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLDeleteRecognizer; +import io.seata.sqlparser.SQLRecognizer; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +/** + * The type MultiSql executor. + * + * @param the type parameter + * @param the type parameter + * @author wangwei.ying + */ +public class MultiDeleteExecutor extends AbstractDMLBaseExecutor { + public MultiDeleteExecutor(StatementProxy statementProxy, StatementCallback statementCallback, List sqlRecognizers) { + super(statementProxy, statementCallback, sqlRecognizers); + } + + @Override + protected TableRecords beforeImage() throws SQLException { + if (sqlRecognizers.size() == 1) { + DeleteExecutor executor = new DeleteExecutor(statementProxy, statementCallback, sqlRecognizers.get(0)); + return executor.beforeImage(); + } + final TableMeta tmeta = getTableMeta(sqlRecognizers.get(0).getTableName()); + final ArrayList> paramAppenderList = new ArrayList<>(); + StringBuilder whereCondition = new StringBuilder(); + for (SQLRecognizer recognizer : sqlRecognizers) { + sqlRecognizer = recognizer; + SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) recognizer; + + ParametersHolder parametersHolder = statementProxy instanceof ParametersHolder ? (ParametersHolder)statementProxy : null; + if (StringUtils.isNotBlank(visitor.getLimit(parametersHolder, paramAppenderList))) { + throw new NotSupportYetException("Multi delete SQL with limit condition is not support yet !"); + } + if (StringUtils.isNotBlank(visitor.getOrderBy())) { + throw new NotSupportYetException("Multi delete SQL with orderBy condition is not support yet !"); + } + + String whereConditionStr = buildWhereCondition(visitor, paramAppenderList); + if (StringUtils.isBlank(whereConditionStr)) { + whereCondition = new StringBuilder(); + paramAppenderList.clear(); + break; + } + if (whereCondition.length() > 0) { + whereCondition.append(" OR "); + } + whereCondition.append(whereConditionStr); + } + StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL()); + if (whereCondition.length() > 0) { + suffix.append(" WHERE ").append(whereCondition); + } + suffix.append(" FOR UPDATE"); + final StringJoiner selectSQLAppender = new StringJoiner(", ", "SELECT ", suffix.toString()); + for (String column : tmeta.getAllColumns().keySet()) { + selectSQLAppender.add(getColumnNameInSQL(ColumnUtils.addEscape(column, getDbType()))); + } + return buildTableRecords(tmeta, selectSQLAppender.toString(), paramAppenderList); + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + return TableRecords.empty(getTableMeta(sqlRecognizers.get(0).getTableName())); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiExecutor.java new file mode 100644 index 0000000..52c0290 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiExecutor.java @@ -0,0 +1,141 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; + +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The type MultiSql executor. now just support same type + * ex. + *
    + *  jdbcTemplate.update("update account_tbl set money = money - ? where user_id = ?;update account_tbl set money = money - ? where user_id = ?", new Object[] {money, userId,"U10000",money,"U1000"});
    + *  
    + * + * @param the type parameter + * @param the type parameter + * @author wangwei.ying + */ +public class MultiExecutor extends AbstractDMLBaseExecutor { + + private Map> multiSqlGroup = new HashMap<>(4); + private Map beforeImagesMap = new HashMap<>(4); + private Map afterImagesMap = new HashMap<>(4); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizers the sql recognizers + */ + public MultiExecutor(StatementProxy statementProxy, StatementCallback statementCallback, List sqlRecognizers) { + super(statementProxy, statementCallback, sqlRecognizers); + } + + /** + * Before image table records. only support update or deleted + * + * @return the table records + * @throws SQLException the sql exception + * @see io.seata.rm.datasource.sql.SQLVisitorFactory#get(String, String) validate sqlType + */ + @Override + protected TableRecords beforeImage() throws SQLException { + //group by sqlType + multiSqlGroup = sqlRecognizers.stream().collect(Collectors.groupingBy(t -> t.getTableName())); + AbstractDMLBaseExecutor executor = null; + for (List value : multiSqlGroup.values()) { + switch (value.get(0).getSQLType()) { + case UPDATE: + executor = new MultiUpdateExecutor(statementProxy, statementCallback, value); + break; + case DELETE: + executor = new MultiDeleteExecutor(statementProxy, statementCallback, value); + break; + default: + throw new UnsupportedOperationException("not support sql" + value.get(0).getOriginalSQL()); + } + TableRecords beforeImage = executor.beforeImage(); + beforeImagesMap.put(value.get(0), beforeImage); + } + return null; + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + AbstractDMLBaseExecutor executor = null; + for (List value : multiSqlGroup.values()) { + switch (value.get(0).getSQLType()) { + case UPDATE: + executor = new MultiUpdateExecutor(statementProxy, statementCallback, value); + break; + case DELETE: + executor = new MultiDeleteExecutor(statementProxy, statementCallback, value); + break; + default: + throw new UnsupportedOperationException("not support sql" + value.get(0).getOriginalSQL()); + } + beforeImage = beforeImagesMap.get(value.get(0)); + TableRecords afterImage = executor.afterImage(beforeImage); + afterImagesMap.put(value.get(0), afterImage); + } + return null; + } + + + @Override + protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException { + if (beforeImagesMap == null || afterImagesMap == null) { + throw new IllegalStateException("images can not be null"); + } + SQLRecognizer recognizer; + for (Map.Entry entry : beforeImagesMap.entrySet()) { + sqlRecognizer = recognizer = entry.getKey(); + beforeImage = entry.getValue(); + afterImage = afterImagesMap.get(recognizer); + if (SQLType.UPDATE == sqlRecognizer.getSQLType()) { + if (beforeImage.getRows().size() != afterImage.getRows().size()) { + throw new ShouldNeverHappenException("Before image size is not equaled to after image size, probably because you updated the primary keys."); + } + } + super.prepareUndoLog(beforeImage, afterImage); + } + } + + public Map> getMultiSqlGroup() { + return multiSqlGroup; + } + + public Map getBeforeImagesMap() { + return beforeImagesMap; + } + + public Map getAfterImagesMap() { + return afterImagesMap; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiUpdateExecutor.java new file mode 100644 index 0000000..ff69eba --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/MultiUpdateExecutor.java @@ -0,0 +1,179 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.StringJoiner; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.util.IOUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.common.DefaultValues; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLUpdateRecognizer; + +/** + * The type MultiSql executor. + * + * @param the type parameter + * @param the type parameter + * @author wangwei-ying + */ +public class MultiUpdateExecutor extends AbstractDMLBaseExecutor { + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private static final boolean ONLY_CARE_UPDATE_COLUMNS = CONFIG.getBoolean( + ConfigurationKeys.TRANSACTION_UNDO_ONLY_CARE_UPDATE_COLUMNS, DefaultValues.DEFAULT_ONLY_CARE_UPDATE_COLUMNS); + + /** + * Instantiates a new Multi update executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizers the sql recognizers + */ + public MultiUpdateExecutor(StatementProxy statementProxy, StatementCallback statementCallback, List sqlRecognizers) { + super(statementProxy, statementCallback, sqlRecognizers); + } + + @Override + protected TableRecords beforeImage() throws SQLException { + if (sqlRecognizers.size() == 1) { + UpdateExecutor executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizers.get(0)); + return executor.beforeImage(); + } + final TableMeta tmeta = getTableMeta(sqlRecognizers.get(0).getTableName()); + + final ArrayList> paramAppenderList = new ArrayList<>(); + Set updateColumnsSet = new HashSet<>(); + StringBuilder whereCondition = new StringBuilder(); + boolean noWhereCondition = false; + for (SQLRecognizer recognizer : sqlRecognizers) { + sqlRecognizer = recognizer; + SQLUpdateRecognizer sqlUpdateRecognizer = (SQLUpdateRecognizer) recognizer; + + ParametersHolder parametersHolder = statementProxy instanceof ParametersHolder ? (ParametersHolder)statementProxy : null; + if (StringUtils.isNotBlank(sqlUpdateRecognizer.getLimit(parametersHolder, paramAppenderList))) { + throw new NotSupportYetException("Multi update SQL with limit condition is not support yet !"); + } + if (StringUtils.isNotBlank(sqlUpdateRecognizer.getOrderBy())) { + throw new NotSupportYetException("Multi update SQL with orderBy condition is not support yet !"); + } + + List updateColumns = sqlUpdateRecognizer.getUpdateColumns(); + updateColumnsSet.addAll(updateColumns); + if (noWhereCondition) { + continue; + } + String whereConditionStr = buildWhereCondition(sqlUpdateRecognizer, paramAppenderList); + if (StringUtils.isBlank(whereConditionStr)) { + noWhereCondition = true; + } else { + if (whereCondition.length() > 0) { + whereCondition.append(" OR "); + } + whereCondition.append(whereConditionStr); + } + } + StringBuilder prefix = new StringBuilder("SELECT "); + final StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL()); + if (noWhereCondition) { + //select all rows + paramAppenderList.clear(); + } else { + suffix.append(" WHERE ").append(whereCondition); + } + suffix.append(" FOR UPDATE"); + final StringJoiner selectSQLAppender = new StringJoiner(", ", prefix, suffix.toString()); + if (ONLY_CARE_UPDATE_COLUMNS) { + if (!containsPK(new ArrayList<>(updateColumnsSet))) { + selectSQLAppender.add(getColumnNamesInSQL(tmeta.getEscapePkNameList(getDbType()))); + } + for (String updateCol : updateColumnsSet) { + selectSQLAppender.add(updateCol); + } + } else { + for (String columnName : tmeta.getAllColumns().keySet()) { + selectSQLAppender.add(ColumnUtils.addEscape(columnName, getDbType())); + } + } + return buildTableRecords(tmeta, selectSQLAppender.toString(), paramAppenderList); + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + if (sqlRecognizers.size() == 1) { + UpdateExecutor executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizers.get(0)); + return executor.afterImage(beforeImage); + } + if (beforeImage == null || beforeImage.size() == 0) { + return TableRecords.empty(getTableMeta(sqlRecognizers.get(0).getTableName())); + } + TableMeta tmeta = getTableMeta(sqlRecognizers.get(0).getTableName()); + String selectSQL = buildAfterImageSQL(tmeta, beforeImage); + ResultSet rs = null; + try (PreparedStatement pst = statementProxy.getConnection().prepareStatement(selectSQL);) { + SqlGenerateUtils.setParamForPk(beforeImage.pkRows(), getTableMeta().getPrimaryKeyOnlyName(), pst); + rs = pst.executeQuery(); + return TableRecords.buildRecords(tmeta, rs); + } finally { + IOUtil.close(rs); + } + } + + private String buildAfterImageSQL(TableMeta tableMeta, TableRecords beforeImage) throws SQLException { + + Set updateColumnsSet = new HashSet<>(); + for (SQLRecognizer recognizer : sqlRecognizers) { + sqlRecognizer = recognizer; + SQLUpdateRecognizer sqlUpdateRecognizer = (SQLUpdateRecognizer) sqlRecognizer; + updateColumnsSet.addAll(sqlUpdateRecognizer.getUpdateColumns()); + } + StringBuilder prefix = new StringBuilder("SELECT "); + String suffix = " FROM " + getFromTableInSQL() + " WHERE " + SqlGenerateUtils.buildWhereConditionByPKs(tableMeta.getPrimaryKeyOnlyName(), beforeImage.pkRows().size(), getDbType()); + StringJoiner selectSQLJoiner = new StringJoiner(", ", prefix.toString(), suffix); + if (ONLY_CARE_UPDATE_COLUMNS) { + if (!containsPK(new ArrayList<>(updateColumnsSet))) { + selectSQLJoiner.add(getColumnNamesInSQL(tableMeta.getEscapePkNameList(getDbType()))); + } + for (String updateCol : updateColumnsSet) { + selectSQLJoiner.add(updateCol); + } + } else { + for (String columnName : tableMeta.getAllColumns().keySet()) { + selectSQLJoiner.add(ColumnUtils.addEscape(columnName, getDbType())); + } + } + return selectSQLJoiner.toString(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/PlainExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/PlainExecutor.java new file mode 100644 index 0000000..48bf88a --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/PlainExecutor.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.Statement; + +import io.seata.rm.datasource.StatementProxy; + +/** + * The type Plain executor. + * + * @author sharajava + * + * @param the type parameter + * @param the type parameter + */ +public class PlainExecutor implements Executor { + + private StatementProxy statementProxy; + + private StatementCallback statementCallback; + + /** + * Instantiates a new Plain executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + */ + public PlainExecutor(StatementProxy statementProxy, StatementCallback statementCallback) { + this.statementProxy = statementProxy; + this.statementCallback = statementCallback; + } + + @Override + public T execute(Object... args) throws Throwable { + return statementCallback.execute(statementProxy.getTargetStatement(), args); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/SelectForUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/SelectForUpdateExecutor.java new file mode 100644 index 0000000..bb2fe26 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/SelectForUpdateExecutor.java @@ -0,0 +1,147 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLSelectRecognizer; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Select for update executor. + * + * @param the type parameter + * @author sharajava + */ +public class SelectForUpdateExecutor extends BaseTransactionalExecutor { + + private static final Logger LOGGER = LoggerFactory.getLogger(SelectForUpdateExecutor.class); + + /** + * Instantiates a new Select for update executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public SelectForUpdateExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + public T doExecute(Object... args) throws Throwable { + Connection conn = statementProxy.getConnection(); + DatabaseMetaData dbmd = conn.getMetaData(); + T rs; + Savepoint sp = null; + boolean originalAutoCommit = conn.getAutoCommit(); + try { + if (originalAutoCommit) { + /* + * In order to hold the local db lock during global lock checking + * set auto commit value to false first if original auto commit was true + */ + conn.setAutoCommit(false); + } else if (dbmd.supportsSavepoints()) { + /* + * In order to release the local db lock when global lock conflict + * create a save point if original auto commit was false, then use the save point here to release db + * lock during global lock checking if necessary + */ + sp = conn.setSavepoint(); + } else { + throw new SQLException("not support savepoint. please check your db version"); + } + + LockRetryController lockRetryController = new LockRetryController(); + ArrayList> paramAppenderList = new ArrayList<>(); + String selectPKSQL = buildSelectSQL(paramAppenderList); + while (true) { + try { + // #870 + // execute return Boolean + // executeQuery return ResultSet + rs = statementCallback.execute(statementProxy.getTargetStatement(), args); + + // Try to get global lock of those rows selected + TableRecords selectPKRows = buildTableRecords(getTableMeta(), selectPKSQL, paramAppenderList); + String lockKeys = buildLockKey(selectPKRows); + if (StringUtils.isNullOrEmpty(lockKeys)) { + break; + } + + if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) { + // Do the same thing under either @GlobalTransactional or @GlobalLock, + // that only check the global lock here. + statementProxy.getConnectionProxy().checkLock(lockKeys); + } else { + throw new RuntimeException("Unknown situation!"); + } + break; + } catch (LockConflictException lce) { + if (sp != null) { + conn.rollback(sp); + } else { + conn.rollback(); + } + // trigger retry + lockRetryController.sleep(lce); + } + } + } finally { + if (sp != null) { + try { + if (!JdbcConstants.ORACLE.equalsIgnoreCase(getDbType())) { + conn.releaseSavepoint(sp); + } + } catch (SQLException e) { + LOGGER.error("{} release save point error.", getDbType(), e); + } + } + if (originalAutoCommit) { + conn.setAutoCommit(true); + } + } + return rs; + } + + private String buildSelectSQL(ArrayList> paramAppenderList) { + SQLSelectRecognizer recognizer = (SQLSelectRecognizer)sqlRecognizer; + StringBuilder selectSQLAppender = new StringBuilder("SELECT "); + selectSQLAppender.append(getColumnNamesInSQL(getTableMeta().getEscapePkNameList(getDbType()))); + selectSQLAppender.append(" FROM ").append(getFromTableInSQL()); + String whereCondition = buildWhereCondition(recognizer, paramAppenderList); + if (StringUtils.isNotBlank(whereCondition)) { + selectSQLAppender.append(" WHERE ").append(whereCondition); + } + selectSQLAppender.append(" FOR UPDATE"); + return selectSQLAppender.toString(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/StatementCallback.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/StatementCallback.java new file mode 100644 index 0000000..208bf69 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/StatementCallback.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; +import java.sql.Statement; + +/** + * The interface Statement callback. + * + * @author sharajava + * + * @param the type parameter + * @param the type parameter + */ +public interface StatementCallback { + + /** + * Execute t. + * + * @param statement the statement + * @param args the args + * @return the t + * @throws SQLException the sql exception + */ + T execute(S statement, Object... args) throws SQLException; +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/UpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/UpdateExecutor.java new file mode 100644 index 0000000..a6ba5ac --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/UpdateExecutor.java @@ -0,0 +1,149 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; + +import io.seata.common.util.IOUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.common.DefaultValues; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLUpdateRecognizer; + +/** + * The type Update executor. + * + * @param the type parameter + * @param the type parameter + * @author sharajava + */ +public class UpdateExecutor extends AbstractDMLBaseExecutor { + + private static final Configuration CONFIG = ConfigurationFactory.getInstance(); + + private static final boolean ONLY_CARE_UPDATE_COLUMNS = CONFIG.getBoolean( + ConfigurationKeys.TRANSACTION_UNDO_ONLY_CARE_UPDATE_COLUMNS, DefaultValues.DEFAULT_ONLY_CARE_UPDATE_COLUMNS); + + /** + * Instantiates a new Update executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public UpdateExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + protected TableRecords beforeImage() throws SQLException { + ArrayList> paramAppenderList = new ArrayList<>(); + TableMeta tmeta = getTableMeta(); + String selectSQL = buildBeforeImageSQL(tmeta, paramAppenderList); + return buildTableRecords(tmeta, selectSQL, paramAppenderList); + } + + private String buildBeforeImageSQL(TableMeta tableMeta, ArrayList> paramAppenderList) { + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) sqlRecognizer; + List updateColumns = recognizer.getUpdateColumns(); + StringBuilder prefix = new StringBuilder("SELECT "); + StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL()); + String whereCondition = buildWhereCondition(recognizer, paramAppenderList); + if (StringUtils.isNotBlank(whereCondition)) { + suffix.append(WHERE).append(whereCondition); + } + String orderBy = recognizer.getOrderBy(); + if (StringUtils.isNotBlank(orderBy)) { + suffix.append(orderBy); + } + ParametersHolder parametersHolder = statementProxy instanceof ParametersHolder ? (ParametersHolder)statementProxy : null; + String limit = recognizer.getLimit(parametersHolder, paramAppenderList); + if (StringUtils.isNotBlank(limit)) { + suffix.append(limit); + } + suffix.append(" FOR UPDATE"); + StringJoiner selectSQLJoin = new StringJoiner(", ", prefix.toString(), suffix.toString()); + if (ONLY_CARE_UPDATE_COLUMNS) { + if (!containsPK(updateColumns)) { + selectSQLJoin.add(getColumnNamesInSQL(tableMeta.getEscapePkNameList(getDbType()))); + } + for (String columnName : updateColumns) { + selectSQLJoin.add(columnName); + } + } else { + for (String columnName : tableMeta.getAllColumns().keySet()) { + selectSQLJoin.add(ColumnUtils.addEscape(columnName, getDbType())); + } + } + return selectSQLJoin.toString(); + } + + @Override + protected TableRecords afterImage(TableRecords beforeImage) throws SQLException { + TableMeta tmeta = getTableMeta(); + if (beforeImage == null || beforeImage.size() == 0) { + return TableRecords.empty(getTableMeta()); + } + String selectSQL = buildAfterImageSQL(tmeta, beforeImage); + ResultSet rs = null; + try (PreparedStatement pst = statementProxy.getConnection().prepareStatement(selectSQL)) { + SqlGenerateUtils.setParamForPk(beforeImage.pkRows(), getTableMeta().getPrimaryKeyOnlyName(), pst); + rs = pst.executeQuery(); + return TableRecords.buildRecords(tmeta, rs); + } finally { + IOUtil.close(rs); + } + } + + private String buildAfterImageSQL(TableMeta tableMeta, TableRecords beforeImage) throws SQLException { + StringBuilder prefix = new StringBuilder("SELECT "); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(tableMeta.getPrimaryKeyOnlyName(), beforeImage.pkRows().size(), getDbType()); + String suffix = " FROM " + getFromTableInSQL() + " WHERE " + whereSql; + StringJoiner selectSQLJoiner = new StringJoiner(", ", prefix.toString(), suffix); + if (ONLY_CARE_UPDATE_COLUMNS) { + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) sqlRecognizer; + List updateColumns = recognizer.getUpdateColumns(); + if (!containsPK(updateColumns)) { + selectSQLJoiner.add(getColumnNamesInSQL(tableMeta.getEscapePkNameList(getDbType()))); + } + for (String columnName : updateColumns) { + selectSQLJoiner.add(columnName); + } + } else { + for (String columnName : tableMeta.getAllColumns().keySet()) { + selectSQLJoiner.add(ColumnUtils.addEscape(columnName, getDbType())); + } + } + return selectSQLJoiner.toString(); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/mysql/MySQLInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/mysql/MySQLInsertExecutor.java new file mode 100644 index 0000000..710ff54 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/mysql/MySQLInsertExecutor.java @@ -0,0 +1,229 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec.mysql; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.BaseInsertExecutor; +import io.seata.rm.datasource.exec.StatementCallback; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.struct.Defaultable; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.struct.SqlMethodExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.HashSet; +import java.util.Set; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The type My sql insert executor. + * + * @author jsbxyyx + */ +@LoadLevel(name = JdbcConstants.MYSQL, scope = Scope.PROTOTYPE) +public class MySQLInsertExecutor extends BaseInsertExecutor implements Defaultable { + + private static final Logger LOGGER = LoggerFactory.getLogger(MySQLInsertExecutor.class); + + /** + * the modify for test + */ + public static final String ERR_SQL_STATE = "S1009"; + + /** + * The cache of auto increment step of database + * the key is the db's resource id + * the value is the step + */ + public static final Map RESOURCE_ID_STEP_CACHE = new ConcurrentHashMap<>(8); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public MySQLInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + public Map> getPkValues() throws SQLException { + Map> pkValuesMap = null; + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + Boolean isContainsPk = containsPK(); + //when there is only one pk in the table + if (getTableMeta().getPrimaryKeyOnlyName().size() == 1) { + if (isContainsPk) { + pkValuesMap = getPkValuesByColumn(); + } + else if (containsColumns()) { + pkValuesMap = getPkValuesByAuto(); + } + else { + pkValuesMap = getPkValuesByColumn(); + } + } else { + //when there is multiple pk in the table + //1,all pk columns are filled value. + //2,the auto increment pk column value is null, and other pk value are not null. + pkValuesMap = getPkValuesByColumn(); + for (String columnName:pkColumnNameList) { + if (!pkValuesMap.containsKey(columnName)) { + ColumnMeta pkColumnMeta = getTableMeta().getColumnMeta(columnName); + if (Objects.nonNull(pkColumnMeta) && pkColumnMeta.isAutoincrement()) { + //3,the auto increment pk column is not exits in sql, and other pk are exits also the value is not null. + pkValuesMap.putAll(getPkValuesByAuto()); + } + } + } + } + return pkValuesMap; + } + + /** + * the modify for test + */ + public Map> getPkValuesByAuto() throws SQLException { + // PK is just auto generated + Map> pkValuesMap = new HashMap<>(8); + Map pkMetaMap = getTableMeta().getPrimaryKeyMap(); + String autoColumnName = null; + for (Map.Entry entry : pkMetaMap.entrySet()) { + if (entry.getValue().isAutoincrement()) { + autoColumnName = entry.getKey(); + break; + } + } + if (StringUtils.isBlank(autoColumnName)) { + throw new ShouldNeverHappenException(); + } + + ResultSet genKeys; + try { + genKeys = statementProxy.getGeneratedKeys(); + } catch (SQLException e) { + // java.sql.SQLException: Generated keys not requested. You need to + // specify Statement.RETURN_GENERATED_KEYS to + // Statement.executeUpdate() or Connection.prepareStatement(). + if (ERR_SQL_STATE.equalsIgnoreCase(e.getSQLState())) { + LOGGER.error("Fail to get auto-generated keys, use 'SELECT LAST_INSERT_ID()' instead. Be cautious, " + + "statement could be polluted. Recommend you set the statement to return generated keys."); + int updateCount = statementProxy.getUpdateCount(); + ResultSet firstId = genKeys = statementProxy.getTargetStatement().executeQuery("SELECT LAST_INSERT_ID()"); + + // If there is batch insert + // do auto increment base LAST_INSERT_ID and variable `auto_increment_increment` + if (updateCount > 1 && canAutoIncrement(pkMetaMap)) { + firstId.next(); + return autoGeneratePks(new BigDecimal(firstId.getString(1)), autoColumnName, updateCount); + } + } else { + throw e; + } + } + List pkValues = new ArrayList<>(); + while (genKeys.next()) { + Object v = genKeys.getObject(1); + pkValues.add(v); + } + try { + genKeys.beforeFirst(); + } catch (SQLException e) { + LOGGER.warn("Fail to reset ResultSet cursor. can not get primary key value"); + } + pkValuesMap.put(autoColumnName,pkValues); + return pkValuesMap; + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + Set keySet = new HashSet<>(pkValuesMap.keySet()); + //auto increment + for (String pkKey:keySet) { + List pkValues = pkValuesMap.get(pkKey); + // pk auto generated while single insert primary key is expression + if (pkValues.size() == 1 && (pkValues.get(0) instanceof SqlMethodExpr)) { + pkValuesMap.putAll(getPkValuesByAuto()); + } + // pk auto generated while column exists and value is null + else if (!pkValues.isEmpty() && pkValues.get(0) instanceof Null) { + pkValuesMap.putAll(getPkValuesByAuto()); + } + } + return pkValuesMap; + } + + @Override + public List getPkValuesByDefault() throws SQLException { + // mysql default keyword the logic not support. (sample: insert into test(id, name) values(default, 'xx')) + throw new NotSupportYetException(); + } + + protected Map> autoGeneratePks(BigDecimal cursor, String autoColumnName, Integer updateCount) throws SQLException { + BigDecimal step = BigDecimal.ONE; + String resourceId = statementProxy.getConnectionProxy().getDataSourceProxy().getResourceId(); + if (RESOURCE_ID_STEP_CACHE.containsKey(resourceId)) { + step = RESOURCE_ID_STEP_CACHE.get(resourceId); + } else { + ResultSet increment = statementProxy.getTargetStatement().executeQuery("SHOW VARIABLES LIKE 'auto_increment_increment'"); + + increment.next(); + step = new BigDecimal(increment.getString(2)); + RESOURCE_ID_STEP_CACHE.put(resourceId, step); + } + + List pkValues = new ArrayList<>(); + for (int i = 0; i < updateCount; i++) { + pkValues.add(cursor); + cursor = cursor.add(step); + } + + Map> pkValuesMap = new HashMap<>(1, 1.001f); + pkValuesMap.put(autoColumnName,pkValues); + return pkValuesMap; + } + + protected boolean canAutoIncrement(Map primaryKeyMap) { + if (primaryKeyMap.size() != 1) { + return false; + } + + for (ColumnMeta pk : primaryKeyMap.values()) { + return pk.isAutoincrement(); + } + return false; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/oracle/OracleInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/oracle/OracleInsertExecutor.java new file mode 100644 index 0000000..00e3ac1 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/oracle/OracleInsertExecutor.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec.oracle; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.BaseInsertExecutor; +import io.seata.rm.datasource.exec.StatementCallback; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.struct.Sequenceable; +import io.seata.sqlparser.struct.SqlMethodExpr; +import io.seata.sqlparser.struct.SqlSequenceExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * The type Oracle insert executor. + * + * @author jsbxyyx + */ +@LoadLevel(name = JdbcConstants.ORACLE, scope = Scope.PROTOTYPE) +public class OracleInsertExecutor extends BaseInsertExecutor implements Sequenceable { + + private static final Logger LOGGER = LoggerFactory.getLogger(OracleInsertExecutor.class); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public OracleInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + public Map> getPkValues() throws SQLException { + Map> pkValuesMap = null; + Boolean isContainsPk = containsPK(); + //when there is only one pk in the table + if (isContainsPk) { + pkValuesMap = getPkValuesByColumn(); + } + else if (containsColumns()) { + String columnName = getTableMeta().getPrimaryKeyOnlyName().get(0); + pkValuesMap = Collections.singletonMap(columnName, getGeneratedKeys()); + } + else { + pkValuesMap = getPkValuesByColumn(); + } + return pkValuesMap; + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + String pkKey = pkValuesMap.keySet().iterator().next(); + List pkValues = pkValuesMap.get(pkKey); + + if (!pkValues.isEmpty() && pkValues.get(0) instanceof SqlSequenceExpr) { + pkValuesMap.put(pkKey,getPkValuesBySequence((SqlSequenceExpr) pkValues.get(0))); + } else if (pkValues.size() == 1 && pkValues.get(0) instanceof SqlMethodExpr) { + pkValuesMap.put(pkKey,getGeneratedKeys()); + } else if (pkValues.size() == 1 && pkValues.get(0) instanceof Null) { + throw new NotSupportYetException("oracle not support null"); + } + + return pkValuesMap; + } + + @Override + public String getSequenceSql(SqlSequenceExpr expr) { + return "SELECT " + expr.getSequence() + ".currval FROM DUAL"; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/exec/postgresql/PostgresqlInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/postgresql/PostgresqlInsertExecutor.java new file mode 100644 index 0000000..0db959e --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/exec/postgresql/PostgresqlInsertExecutor.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec.postgresql; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.loader.Scope; +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.BaseInsertExecutor; +import io.seata.rm.datasource.exec.StatementCallback; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.struct.Defaultable; +import io.seata.sqlparser.struct.Sequenceable; +import io.seata.sqlparser.struct.SqlMethodExpr; +import io.seata.sqlparser.struct.SqlSequenceExpr; +import io.seata.sqlparser.struct.SqlDefaultExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * The type Postgresql insert executor. + * + * @author jsbxyyx + */ +@LoadLevel(name = JdbcConstants.POSTGRESQL, scope = Scope.PROTOTYPE) +public class PostgresqlInsertExecutor extends BaseInsertExecutor implements Sequenceable, Defaultable { + + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlInsertExecutor.class); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public PostgresqlInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + @Override + public Map> getPkValues() throws SQLException { + Map> pkValuesMap = null; + Boolean isContainsPk = containsPK(); + //when there is only one pk in the table + if (isContainsPk) { + pkValuesMap = getPkValuesByColumn(); + } + else if (containsColumns()) { + String columnName = getTableMeta().getPrimaryKeyOnlyName().get(0); + pkValuesMap = Collections.singletonMap(columnName, getGeneratedKeys()); + } + else { + pkValuesMap = getPkValuesByColumn(); + } + return pkValuesMap; + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + String pkKey = pkValuesMap.keySet().iterator().next(); + List pkValues = pkValuesMap.get(pkKey); + if (!pkValues.isEmpty() && pkValues.get(0) instanceof SqlSequenceExpr) { + pkValuesMap.put(pkKey,getPkValuesBySequence((SqlSequenceExpr) pkValues.get(0))); + } else if (!pkValues.isEmpty() && pkValues.get(0) instanceof SqlMethodExpr) { + pkValuesMap.put(pkKey,getGeneratedKeys()); + } else if (!pkValues.isEmpty() && pkValues.get(0) instanceof SqlDefaultExpr) { + pkValuesMap.put(pkKey,getPkValuesByDefault()); + } + + return pkValuesMap; + } + + /** + * get primary key values by default + * @return + * @throws SQLException + */ + @Override + public List getPkValuesByDefault() throws SQLException { + // current version 1.2 only support postgresql. + Map pkMetaMap = getTableMeta().getPrimaryKeyMap(); + ColumnMeta pkMeta = pkMetaMap.values().iterator().next(); + String columnDef = pkMeta.getColumnDef(); + // sample: nextval('test_id_seq'::regclass) + String seq = org.apache.commons.lang.StringUtils.substringBetween(columnDef, "'", "'"); + String function = org.apache.commons.lang.StringUtils.substringBetween(columnDef, "", "("); + if (StringUtils.isBlank(seq)) { + throw new ShouldNeverHappenException("get primary key value failed, cause columnDef is " + columnDef); + } + return getPkValuesBySequence(new SqlSequenceExpr("'" + seq + "'", function)); + } + + @Override + public String getSequenceSql(SqlSequenceExpr expr) { + return "SELECT currval(" + expr.getSequence() + ")"; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManager.java new file mode 100644 index 0000000..a898bd1 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManager.java @@ -0,0 +1,36 @@ +package io.seata.rm.datasource.extend; + + +import io.seata.core.exception.TransactionException; +import io.seata.rm.datasource.ConnectionProxy; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Seata 自定义扩展管理器 + * + * @author wangchunxiang + * @date 2024/08/07 + */ +public interface FtbSeataExtendManager { + + /** + * Flush undo logs. + * + * @param cp the cp + * @param xid the xid + * @throws SQLException SQLException + */ + void flushUndoLogsConnection(ConnectionProxy cp,String xid) throws SQLException; + + /** + * Delete undo log. + * + * @param xid the xid + * @param conn the conn + * @throws SQLException the sql exception + */ + void undoLogConnection(String xid, Connection conn) throws TransactionException; + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManagerHolder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManagerHolder.java new file mode 100644 index 0000000..8b6074c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/extend/FtbSeataExtendManagerHolder.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.extend; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.core.model.TransactionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The type Default FtbSeataExtend manager. + * + * @author wangchunxiang + * @date 2024/08/07 + */ +public class FtbSeataExtendManagerHolder { + + private static final Logger LOGGER = LoggerFactory.getLogger(FtbSeataExtendManager.class); + + private static class SingletonHolder { + + private static FtbSeataExtendManager INSTANCE = null; + + static { + try { + INSTANCE = EnhancedServiceLoader.load(FtbSeataExtendManager.class); + LOGGER.info("FtbSeataExtendManager Singleton {}", INSTANCE); + } catch (Throwable anyEx) { + LOGGER.error("Failed to load FtbSeataExtendManager Singleton! ", anyEx); + } + } + } + + /** + * Get FtbSeataExtend manager. + * + * @return the FtbSeataExtend manager + */ + public static FtbSeataExtendManager get() { + if (SingletonHolder.INSTANCE == null) { + throw new ShouldNeverHappenException("FtbSeataExtendManager is NOT ready!"); + } + return SingletonHolder.INSTANCE; + } + + /** + * Set a TM instance. + * + * @param mock commonly used for test mocking + */ + public static void set(FtbSeataExtendManager mock) { + SingletonHolder.INSTANCE = mock; + } + + private FtbSeataExtendManagerHolder() { + + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/SQLVisitorFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/SQLVisitorFactory.java new file mode 100644 index 0000000..4c1d559 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/SQLVisitorFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLRecognizerFactory; +import io.seata.sqlparser.SqlParserType; + +import java.util.List; + +/** + * @author ggndnn + */ +public class SQLVisitorFactory { + /** + * SQLRecognizerFactory. + */ + private final static SQLRecognizerFactory SQL_RECOGNIZER_FACTORY; + + static { + String sqlparserType = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.SQL_PARSER_TYPE, SqlParserType.SQL_PARSER_TYPE_DRUID); + SQL_RECOGNIZER_FACTORY = EnhancedServiceLoader.load(SQLRecognizerFactory.class, sqlparserType); + } + + /** + * Get sql recognizer. + * + * @param sql the sql + * @param dbType the db type + * @return the sql recognizer + */ + public static List get(String sql, String dbType) { + return SQL_RECOGNIZER_FACTORY.create(sql, dbType); + } + + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/serial/SerialArray.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/serial/SerialArray.java new file mode 100644 index 0000000..a9afd5b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/serial/SerialArray.java @@ -0,0 +1,189 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.serial; + +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import javax.sql.rowset.serial.SerialDatalink; +import javax.sql.rowset.serial.SerialException; +import javax.sql.rowset.serial.SerialJavaObject; +import java.net.URL; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Map; + +/** + * used for jdbc type is JDBCType.ARRAY serialize. + * + * @author jsbxyyx + */ +public class SerialArray implements java.sql.Array, java.io.Serializable { + + static final long serialVersionUID = 1L; + + private Object[] elements; + private int baseType; + private String baseTypeName; + private int len; + + public SerialArray() { + } + + public SerialArray(java.sql.Array array) throws SerialException, SQLException { + if (array == null) { + throw new SQLException("Cannot instantiate a SerialArray " + + "object with a null Array object"); + } + + if ((elements = (Object[]) array.getArray()) == null) { + throw new SQLException("Invalid Array object. Calls to Array.getArray() " + + "return null value which cannot be serialized"); + } + + baseType = array.getBaseType(); + baseTypeName = array.getBaseTypeName(); + len = elements.length; + + switch (baseType) { + case java.sql.Types.BLOB: + for (int i = 0; i < len; i++) { + elements[i] = new SerialBlob((Blob) elements[i]); + } + break; + case java.sql.Types.CLOB: + for (int i = 0; i < len; i++) { + elements[i] = new SerialClob((Clob) elements[i]); + } + break; + case java.sql.Types.DATALINK: + for (int i = 0; i < len; i++) { + elements[i] = new SerialDatalink((URL) elements[i]); + } + break; + case java.sql.Types.JAVA_OBJECT: + for (int i = 0; i < len; i++) { + elements[i] = new SerialJavaObject(elements[i]); + } + break; + default: + break; + } + } + + @Override + public String getBaseTypeName() throws SQLException { + return baseTypeName; + } + + public void setBaseTypeName(String baseTypeName) { + this.baseTypeName = baseTypeName; + } + + @Override + public int getBaseType() throws SQLException { + return baseType; + } + + public void setBaseType(int baseType) { + this.baseType = baseType; + } + + @Override + public Object getArray() throws SQLException { + return elements; + } + + @Override + public Object getArray(Map> map) throws SQLException { + return elements; + } + + @Override + public Object getArray(long index, int count) throws SQLException { + return elements; + } + + @Override + public Object getArray(long index, int count, Map> map) throws SQLException { + return elements; + } + + @Override + public ResultSet getResultSet() throws SQLException { + // don't throws exception. + return null; + } + + @Override + public ResultSet getResultSet(Map> map) throws SQLException { + // don't throws exception. + return null; + } + + @Override + public ResultSet getResultSet(long index, int count) throws SQLException { + // don't throws exception. + return null; + } + + @Override + public ResultSet getResultSet(long index, int count, Map> map) throws SQLException { + // don't throws exception. + return null; + } + + @Override + public void free() throws SQLException { + if (elements != null) { + elements = null; + baseTypeName = null; + } + } + + public Object[] getElements() { + return elements; + } + + public void setElements(Object[] elements) { + this.elements = elements; + this.len = elements != null ? elements.length : 0; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof SerialArray) { + SerialArray sa = (SerialArray) obj; + return baseType == sa.baseType && + baseTypeName.equals(sa.baseTypeName) && + Arrays.equals(elements, sa.elements); + } + return false; + } + + @Override + public int hashCode() { + return (((31 + Arrays.hashCode(elements)) * 31 + len) * 31 + + baseType) * 31 + baseTypeName.hashCode(); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/ColumnMeta.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/ColumnMeta.java new file mode 100644 index 0000000..9b34f90 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/ColumnMeta.java @@ -0,0 +1,496 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.util.Objects; + +/** + * The type Column meta. + * + * @author sharajava + */ +public class ColumnMeta { + private String tableCat; + private String tableSchemaName; + private String tableName; + private String columnName; + private int dataType; + private String dataTypeName; + private int columnSize; + private int decimalDigits; + private int numPrecRadix; + private int nullAble; + private String remarks; + private String columnDef; + private int sqlDataType; + private int sqlDatetimeSub; + private Object charOctetLength; + private int ordinalPosition; + private String isNullAble; + private String isAutoincrement; + + /** + * Instantiates a new Column meta. + */ + public ColumnMeta() { + } + + @Override + public String toString() { + return "ColumnMeta{" + + "tableCat='" + tableCat + '\'' + + ", tableSchemaName='" + tableSchemaName + '\'' + + ", tableName='" + tableName + '\'' + + ", columnName='" + columnName + '\'' + + ", dataType=" + dataType + + ", dataTypeName='" + dataTypeName + '\'' + + ", columnSize=" + columnSize + + ", decimalDigits=" + decimalDigits + + ", numPrecRadix=" + numPrecRadix + + ", nullAble=" + nullAble + + ", remarks='" + remarks + '\'' + + ", columnDef='" + columnDef + '\'' + + ", sqlDataType=" + sqlDataType + + ", sqlDatetimeSub=" + sqlDatetimeSub + + ", charOctetLength=" + charOctetLength + + ", ordinalPosition=" + ordinalPosition + + ", isNullAble='" + isNullAble + '\'' + + ", isAutoincrement='" + isAutoincrement + '\'' + + '}'; + } + + /** + * Is autoincrement boolean. + * + * @return the boolean + */ + public boolean isAutoincrement() { + return "YES".equalsIgnoreCase(isAutoincrement); + } + + /** + * Gets table cat. + * + * @return the table cat + */ + public String getTableCat() { + return tableCat; + } + + /** + * Sets table cat. + * + * @param tableCat the table cat + */ + public void setTableCat(String tableCat) { + this.tableCat = tableCat; + } + + /** + * Sets table schema name. + * + * @param tableSchemaName the table schema name + */ + public void setTableSchemaName(String tableSchemaName) { + this.tableSchemaName = tableSchemaName; + } + + /** + * Gets table schema name + * + * @return table schema name + */ + protected String getTableSchemaName() { + return tableSchemaName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets table name + * + * @return table name + */ + protected String getTableName() { + return tableName; + } + + /** + * Gets column name. + * + * @return the column name + */ + public String getColumnName() { + return columnName; + } + + /** + * Sets column name. + * + * @param columnName the column name + */ + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + /** + * Gets data type. + * + * @return the data type + */ + public int getDataType() { + return dataType; + } + + /** + * Sets data type. + * + * @param dataType the data type + */ + public void setDataType(int dataType) { + this.dataType = dataType; + } + + /** + * Gets data type name. + * + * @return the data type name + */ + public String getDataTypeName() { + return dataTypeName; + } + + /** + * Sets data type name. + * + * @param dataTypeName the data type name + */ + public void setDataTypeName(String dataTypeName) { + this.dataTypeName = dataTypeName; + } + + /** + * Gets column size. + * + * @return the column size + */ + public int getColumnSize() { + return columnSize; + } + + /** + * Sets column size. + * + * @param columnSize the column size + */ + public void setColumnSize(int columnSize) { + this.columnSize = columnSize; + } + + /** + * Gets decimal digits. + * + * @return the decimal digits + */ + public int getDecimalDigits() { + return decimalDigits; + } + + /** + * Sets decimal digits. + * + * @param decimalDigits the decimal digits + */ + public void setDecimalDigits(int decimalDigits) { + this.decimalDigits = decimalDigits; + } + + /** + * Gets num prec radix. + * + * @return the num prec radix + */ + public int getNumPrecRadix() { + return numPrecRadix; + } + + /** + * Sets num prec radix. + * + * @param numPrecRadix the num prec radix + */ + public void setNumPrecRadix(int numPrecRadix) { + this.numPrecRadix = numPrecRadix; + } + + /** + * Gets null able. + * + * @return the null able + */ + public int getNullAble() { + return nullAble; + } + + /** + * Sets null able. + * + * @param nullAble the null able + */ + public void setNullAble(int nullAble) { + this.nullAble = nullAble; + } + + /** + * Gets remarks. + * + * @return the remarks + */ + public String getRemarks() { + return remarks; + } + + /** + * Sets remarks. + * + * @param remarks the remarks + */ + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + /** + * Gets column def. + * + * @return the column def + */ + public String getColumnDef() { + return columnDef; + } + + /** + * Sets column def. + * + * @param columnDef the column def + */ + public void setColumnDef(String columnDef) { + this.columnDef = columnDef; + } + + /** + * Gets sql data type. + * + * @return the sql data type + */ + public int getSqlDataType() { + return sqlDataType; + } + + /** + * Sets sql data type. + * + * @param sqlDataType the sql data type + */ + public void setSqlDataType(int sqlDataType) { + this.sqlDataType = sqlDataType; + } + + /** + * Gets sql datetime sub. + * + * @return the sql datetime sub + */ + public int getSqlDatetimeSub() { + return sqlDatetimeSub; + } + + /** + * Sets sql datetime sub. + * + * @param sqlDatetimeSub the sql datetime sub + */ + public void setSqlDatetimeSub(int sqlDatetimeSub) { + this.sqlDatetimeSub = sqlDatetimeSub; + } + + /** + * Gets char octet length. + * + * @return the char octet length + */ + public Object getCharOctetLength() { + return charOctetLength; + } + + /** + * Sets char octet length. + * + * @param charOctetLength the char octet length + */ + public void setCharOctetLength(Object charOctetLength) { + this.charOctetLength = charOctetLength; + } + + /** + * Gets ordinal position. + * + * @return the ordinal position + */ + public int getOrdinalPosition() { + return ordinalPosition; + } + + /** + * Sets ordinal position. + * + * @param ordinalPosition the ordinal position + */ + public void setOrdinalPosition(int ordinalPosition) { + this.ordinalPosition = ordinalPosition; + } + + /** + * Gets is null able. + * + * @return the is null able + */ + public String getIsNullAble() { + return isNullAble; + } + + /** + * Sets is null able. + * + * @param isNullAble the is null able + */ + public void setIsNullAble(String isNullAble) { + this.isNullAble = isNullAble; + } + + /** + * Gets is autoincrement. + * + * @return the is autoincrement + */ + public String getIsAutoincrement() { + return isAutoincrement; + } + + /** + * Sets is autoincrement. + * + * @param isAutoincrement the is autoincrement + */ + public void setIsAutoincrement(String isAutoincrement) { + this.isAutoincrement = isAutoincrement; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ColumnMeta)) { + return false; + } + ColumnMeta columnMeta = (ColumnMeta) o; + if (!Objects.equals(columnMeta.tableCat, this.tableCat)) { + return false; + } + if (!Objects.equals(columnMeta.tableSchemaName, this.tableSchemaName)) { + return false; + } + if (!Objects.equals(columnMeta.tableName, this.tableName)) { + return false; + } + if (!Objects.equals(columnMeta.columnName, this.columnName)) { + return false; + } + if (!Objects.equals(columnMeta.dataType, this.dataType)) { + return false; + } + if (!Objects.equals(columnMeta.dataTypeName, this.dataTypeName)) { + return false; + } + if (!Objects.equals(columnMeta.columnSize, this.columnSize)) { + return false; + } + if (!Objects.equals(columnMeta.decimalDigits, this.decimalDigits)) { + return false; + } + if (!Objects.equals(columnMeta.numPrecRadix, this.numPrecRadix)) { + return false; + } + if (!Objects.equals(columnMeta.nullAble, this.nullAble)) { + return false; + } + if (!Objects.equals(columnMeta.remarks, this.remarks)) { + return false; + } + if (!Objects.equals(columnMeta.columnDef, this.columnDef)) { + return false; + } + if (!Objects.equals(columnMeta.sqlDataType, this.sqlDataType)) { + return false; + } + if (!Objects.equals(columnMeta.sqlDatetimeSub, this.sqlDatetimeSub)) { + return false; + } + if (!Objects.equals(columnMeta.charOctetLength, this.charOctetLength)) { + return false; + } + if (!Objects.equals(columnMeta.ordinalPosition, this.ordinalPosition)) { + return false; + } + if (!Objects.equals(columnMeta.isNullAble, this.isNullAble)) { + return false; + } + if (!Objects.equals(columnMeta.isAutoincrement, this.isAutoincrement)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = Objects.hashCode(tableCat); + hash += Objects.hashCode(tableSchemaName); + hash += Objects.hashCode(tableName); + hash += Objects.hashCode(columnName); + hash += Objects.hashCode(dataType); + hash += Objects.hashCode(dataTypeName); + hash += Objects.hashCode(columnSize); + hash += Objects.hashCode(decimalDigits); + hash += Objects.hashCode(numPrecRadix); + hash += Objects.hashCode(nullAble); + hash += Objects.hashCode(remarks); + hash += Objects.hashCode(columnDef); + hash += Objects.hashCode(sqlDataType); + hash += Objects.hashCode(sqlDatetimeSub); + hash += Objects.hashCode(charOctetLength); + hash += Objects.hashCode(ordinalPosition); + hash += Objects.hashCode(isNullAble); + hash += Objects.hashCode(isAutoincrement); + return hash; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Field.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Field.java new file mode 100644 index 0000000..4c6c2bc --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Field.java @@ -0,0 +1,149 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +/** + * Field + * + * @author sharajava + */ +public class Field implements java.io.Serializable { + + private static final long serialVersionUID = -3489407607572041783L; + + /** + * The Name. + */ + private String name; + + private KeyType keyType = KeyType.NULL; + + /** + * The Type. + */ + private int type; + + /** + * The Value. + */ + private Object value; + + /** + * Instantiates a new Field. + */ + public Field() { + } + + /** + * Instantiates a new Field. + * + * @param name the name + * @param type the type + * @param value the value + */ + public Field(String name, int type, Object value) { + this.name = name; + this.type = type; + this.value = value; + } + + /** + * Gets name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Sets name. + * + * @param attrName the attr name + */ + public void setName(String attrName) { + this.name = attrName; + } + + /** + * Gets key type. + * + * @return the key type + */ + public KeyType getKeyType() { + return keyType; + } + + /** + * Sets key type. + * + * @param keyType the key type + */ + public void setKeyType(KeyType keyType) { + this.keyType = keyType; + } + + /** + * Gets type. + * + * @return the type + */ + public int getType() { + return type; + } + + /** + * Sets type. + * + * @param attrType the attr type + */ + public void setType(int attrType) { + this.type = attrType; + } + + /** + * Gets value. + * + * @return the value + */ + public Object getValue() { + return value; + } + + /** + * Sets value. + * + * @param value the value + */ + public void setValue(Object value) { + this.value = value; + } + + /** + * Is key boolean. + * + * @param pkname the pkname + * @return the boolean + */ + public boolean isKey(String pkname) { + return name.equalsIgnoreCase(pkname); + } + + @Override + public String toString() { + return String.format("[%s,%s]", name, value); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexMeta.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexMeta.java new file mode 100644 index 0000000..6d1618c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexMeta.java @@ -0,0 +1,261 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.lang.ArrayUtils; + +/** + * The type Index meta. + * + * @author sharajava + */ +public class IndexMeta { + private List values = new ArrayList(); + + private boolean nonUnique; + private String indexQualifier; + private String indexName; + private short type; + private IndexType indextype; + private String ascOrDesc; + private int cardinality; + private int ordinalPosition; + + /** + * Instantiates a new Index meta. + */ + public IndexMeta() { + } + + /** + * Gets values. + * + * @return the values + */ + public List getValues() { + return values; + } + + /** + * Sets values. + * + * @param values the values + */ + public void setValues(List values) { + this.values = values; + } + + /** + * Is non unique boolean. + * + * @return the boolean + */ + public boolean isNonUnique() { + return nonUnique; + } + + /** + * Sets non unique. + * + * @param nonUnique the non unique + */ + public void setNonUnique(boolean nonUnique) { + this.nonUnique = nonUnique; + } + + /** + * Gets index qualifier. + * + * @return the index qualifier + */ + public String getIndexQualifier() { + return indexQualifier; + } + + /** + * Sets index qualifier. + * + * @param indexQualifier the index qualifier + */ + public void setIndexQualifier(String indexQualifier) { + this.indexQualifier = indexQualifier; + } + + /** + * Gets index name. + * + * @return the index name + */ + public String getIndexName() { + return indexName; + } + + /** + * Sets index name. + * + * @param indexName the index name + */ + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + /** + * Gets type. + * + * @return the type + */ + public short getType() { + return type; + } + + /** + * Sets type. + * + * @param type the type + */ + public void setType(short type) { + this.type = type; + } + + /** + * Gets asc or desc. + * + * @return the asc or desc + */ + public String getAscOrDesc() { + return ascOrDesc; + } + + /** + * Sets asc or desc. + * + * @param ascOrDesc the asc or desc + */ + public void setAscOrDesc(String ascOrDesc) { + this.ascOrDesc = ascOrDesc; + } + + /** + * Gets cardinality. + * + * @return the cardinality + */ + public int getCardinality() { + return cardinality; + } + + /** + * Sets cardinality. + * + * @param cardinality the cardinality + */ + public void setCardinality(int cardinality) { + this.cardinality = cardinality; + } + + /** + * Gets ordinal position. + * + * @return the ordinal position + */ + public int getOrdinalPosition() { + return ordinalPosition; + } + + /** + * Sets ordinal position. + * + * @param ordinalPosition the ordinal position + */ + public void setOrdinalPosition(int ordinalPosition) { + this.ordinalPosition = ordinalPosition; + } + + /** + * Gets indextype. + * + * @return the indextype + */ + public IndexType getIndextype() { + return indextype; + } + + /** + * Sets indextype. + * + * @param indextype the indextype + */ + public void setIndextype(IndexType indextype) { + this.indextype = indextype; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IndexMeta)) { + return false; + } + IndexMeta indexMeta = (IndexMeta)o; + if (!ArrayUtils.isEquals(indexMeta.values, this.values)) { + return false; + } + if (!Objects.equals(indexMeta.nonUnique, this.nonUnique)) { + return false; + } + if (!Objects.equals(indexMeta.indexQualifier, this.indexQualifier)) { + return false; + } + if (!Objects.equals(indexMeta.indexName, this.indexName)) { + return false; + } + if (!Objects.equals(indexMeta.type, this.type)) { + return false; + } + if (!Objects.equals(indexMeta.indextype.value(), this.indextype.value())) { + return false; + } + if (!Objects.equals(indexMeta.ascOrDesc, this.ascOrDesc)) { + return false; + } + if (!Objects.equals(indexMeta.ordinalPosition, this.ordinalPosition)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = Objects.hashCode(nonUnique); + hash += Objects.hashCode(indexQualifier); + hash += Objects.hashCode(indexName); + hash += Objects.hashCode(type); + hash += Objects.hashCode(indextype); + hash += Objects.hashCode(ascOrDesc); + hash += Objects.hashCode(ordinalPosition); + return hash; + } + + @Override + public String toString() { + return "indexName:" + indexName + "->" + "type:" + type + "->" + "values:" + values; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexType.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexType.java new file mode 100644 index 0000000..7c43597 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/IndexType.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +/** + * The enum Index type. + * + * @author sharajava + */ +public enum IndexType { + /** + * Primary index type. + */ + PRIMARY(0), + /** + * Normal index type. + */ + NORMAL(1), + /** + * Unique index type. + */ + UNIQUE(2), + /** + * Full text index type. + */ + FULL_TEXT(3); + + private int i; + + IndexType(int i) { + this.i = i; + } + + /** + * Value int. + * + * @return the int + */ + public int value() { + return this.i; + } + + /** + * Value of index type. + * + * @param i the + * @return the index type + */ + public static IndexType valueOf(int i) { + for (IndexType t : values()) { + if (t.value() == i) { + return t; + } + } + throw new IllegalArgumentException("Invalid IndexType:" + i); + } +} \ No newline at end of file diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/KeyType.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/KeyType.java new file mode 100644 index 0000000..3bce24f --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/KeyType.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +/** + * The enum Key type. + * + * @author sharajava + */ +public enum KeyType { + + /** + * Null key type. + */ + // Null + NULL, + + /** + * The Primary key. + */ + // Primary Key + PRIMARY_KEY +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Row.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Row.java new file mode 100644 index 0000000..28a59e6 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/Row.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.util.ArrayList; +import java.util.List; + + +/** + * The type Row. + * + * @author sharajava + */ +public class Row implements java.io.Serializable { + + private static final long serialVersionUID = 6532477221179419451L; + + private List fields = new ArrayList(); + + /** + * Instantiates a new Row. + */ + public Row() { + } + + /** + * Gets fields. + * + * @return the fields + */ + public List getFields() { + return fields; + } + + /** + * Sets fields. + * + * @param fields the fields + */ + public void setFields(List fields) { + this.fields = fields; + } + + /** + * Add. + * + * @param field the field + */ + public void add(Field field) { + fields.add(field); + } + + /** + * Primary keys list. + * + * @return the Primary keys list + */ + public List primaryKeys() { + List pkFields = new ArrayList<>(); + for (Field field : fields) { + if (KeyType.PRIMARY_KEY == field.getKeyType()) { + pkFields.add(field); + } + } + return pkFields; + } + + /** + * Non-primary keys list. + * + * @return the non-primary list + */ + public List nonPrimaryKeys() { + List nonPkFields = new ArrayList<>(); + for (Field field : fields) { + if (KeyType.PRIMARY_KEY != field.getKeyType()) { + nonPkFields.add(field); + } + } + return nonPkFields; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMeta.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMeta.java new file mode 100644 index 0000000..1f70975 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMeta.java @@ -0,0 +1,209 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; + +/** + * The type Table meta. + * + * @author sharajava + */ +public class TableMeta { + private String tableName; + + /** + * key: column name + */ + + private Map allColumns = new LinkedHashMap<>(); + /** + * key: index name + */ + private Map allIndexes = new LinkedHashMap<>(); + + /** + * Gets table name. + * + * @return the table name + */ + public String getTableName() { + return tableName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets column meta. + * + * @param colName the col name + * @return the column meta + */ + public ColumnMeta getColumnMeta(String colName) { + return allColumns.get(colName); + } + + /** + * Gets all columns. + * + * @return the all columns + */ + public Map getAllColumns() { + return allColumns; + } + + /** + * Gets all indexes. + * + * @return the all indexes + */ + public Map getAllIndexes() { + return allIndexes; + } + + /** + * Gets auto increase column. + * + * @return the auto increase column + */ + public ColumnMeta getAutoIncreaseColumn() { + // TODO: how about auto increment but not pk? + for (Entry entry : allColumns.entrySet()) { + ColumnMeta col = entry.getValue(); + if ("YES".equalsIgnoreCase(col.getIsAutoincrement())) { + return col; + } + } + return null; + } + + /** + * Gets primary key map. + * + * @return the primary key map + */ + public Map getPrimaryKeyMap() { + Map pk = new HashMap<>(); + allIndexes.forEach((key, index) -> { + if (index.getIndextype().value() == IndexType.PRIMARY.value()) { + for (ColumnMeta col : index.getValues()) { + pk.put(col.getColumnName(), col); + } + } + }); + + if (pk.size() < 1) { + throw new NotSupportYetException(String.format("%s needs to contain the primary key.", tableName)); + } + + return pk; + } + + /** + * Gets primary key only name. + * + * @return the primary key only name + */ + @SuppressWarnings("serial") + public List getPrimaryKeyOnlyName() { + List list = new ArrayList<>(); + for (Entry entry : getPrimaryKeyMap().entrySet()) { + list.add(entry.getKey()); + } + return list; + } + + /** + * Gets add escape pk name. + * + * @param dbType the db type + * @return escape pk name list + */ + public List getEscapePkNameList(String dbType) { + return ColumnUtils.addEscape(getPrimaryKeyOnlyName(), dbType); + } + + /** + * Contains pk boolean. + * + * @param cols the cols + * @return the boolean + */ + public boolean containsPK(List cols) { + if (cols == null) { + return false; + } + + List pk = getPrimaryKeyOnlyName(); + if (pk.isEmpty()) { + return false; + } + + + //at least contain one pk + if (cols.containsAll(pk)) { + return true; + } else { + return CollectionUtils.toUpperList(cols).containsAll(CollectionUtils.toUpperList(pk)); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TableMeta)) { + return false; + } + TableMeta tableMeta = (TableMeta) o; + if (!Objects.equals(tableMeta.tableName, this.tableName)) { + return false; + } + if (!Objects.equals(tableMeta.allColumns, this.allColumns)) { + return false; + } + if (!Objects.equals(tableMeta.allIndexes, this.allIndexes)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int hash = Objects.hashCode(tableName); + hash += Objects.hashCode(allColumns); + hash += Objects.hashCode(allIndexes); + return hash; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCache.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCache.java new file mode 100644 index 0000000..c7605d8 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCache.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.sql.Connection; + +/** + * The type Table meta cache. + * + * @author sharajava + */ +public interface TableMetaCache { + + /** + * Gets table meta. + * + * @param connection the connection + * @param tableName the table name + * @param resourceId the resource id + * @return the table meta + */ + TableMeta getTableMeta(Connection connection, String tableName, String resourceId); + + /** + * Clear the table meta cache + * + * @param connection the connection + * @param resourceId the resource id + */ + void refresh(Connection connection, String resourceId); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactory.java new file mode 100644 index 0000000..bd7b7fb --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +/** + * @author guoyao + */ +public class TableMetaCacheFactory { + + private static final Map TABLE_META_CACHE_MAP = new ConcurrentHashMap<>(); + + /** + * get table meta cache + * + * @param dbType the db type + * @return table meta cache + */ + public static TableMetaCache getTableMetaCache(String dbType) { + return CollectionUtils.computeIfAbsent(TABLE_META_CACHE_MAP, dbType, + key -> EnhancedServiceLoader.load(TableMetaCache.class, dbType)); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableRecords.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableRecords.java new file mode 100644 index 0000000..4e7c6e7 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/TableRecords.java @@ -0,0 +1,283 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import javax.sql.rowset.serial.SerialDatalink; +import javax.sql.rowset.serial.SerialJavaObject; +import javax.sql.rowset.serial.SerialRef; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.rm.datasource.sql.serial.SerialArray; + +/** + * The type Table records. + * + * @author sharajava + */ +public class TableRecords implements java.io.Serializable { + + private static final long serialVersionUID = 4441667803166771721L; + + private transient TableMeta tableMeta; + + private String tableName; + + private List rows = new ArrayList(); + + /** + * Gets table name. + * + * @return the table name + */ + public String getTableName() { + return tableName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets rows. + * + * @return the rows + */ + public List getRows() { + return rows; + } + + /** + * Sets rows. + * + * @param rows the rows + */ + public void setRows(List rows) { + this.rows = rows; + } + + /** + * Instantiates a new Table records. + */ + public TableRecords() { + + } + + /** + * Instantiates a new Table records. + * + * @param tableMeta the table meta + */ + public TableRecords(TableMeta tableMeta) { + setTableMeta(tableMeta); + } + + /** + * Sets table meta. + * + * @param tableMeta the table meta + */ + public void setTableMeta(TableMeta tableMeta) { + if (this.tableMeta != null) { + throw new ShouldNeverHappenException(); + } + this.tableMeta = tableMeta; + this.tableName = tableMeta.getTableName(); + } + + /** + * Size int. + * + * @return the int + */ + public int size() { + return rows.size(); + } + + /** + * Add. + * + * @param row the row + */ + public void add(Row row) { + rows.add(row); + } + + /** + * Pk rows list. + * + * @return return a list. each element of list is a map,the map hold the pk column name as a key and field as the value + */ + public List> pkRows() { + final Map primaryKeyMap = getTableMeta().getPrimaryKeyMap(); + List> pkRows = new ArrayList<>(); + for (Row row : rows) { + List fields = row.getFields(); + Map rowMap = new HashMap<>(3); + for (Field field : fields) { + if (primaryKeyMap.containsKey(field.getName())) { + rowMap.put(field.getName(),field); + } + } + pkRows.add(rowMap); + } + return pkRows; + } + + /** + * Gets table meta. + * + * @return the table meta + */ + public TableMeta getTableMeta() { + return tableMeta; + } + + /** + * Empty table records. + * + * @param tableMeta the table meta + * @return the table records + */ + public static TableRecords empty(TableMeta tableMeta) { + return new EmptyTableRecords(tableMeta); + } + + /** + * Build records table records. + * + * @param tmeta the tmeta + * @param resultSet the result set + * @return the table records + * @throws SQLException the sql exception + */ + public static TableRecords buildRecords(TableMeta tmeta, ResultSet resultSet) throws SQLException { + TableRecords records = new TableRecords(tmeta); + ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); + int columnCount = resultSetMetaData.getColumnCount(); + + while (resultSet.next()) { + List fields = new ArrayList<>(columnCount); + for (int i = 1; i <= columnCount; i++) { + String colName = resultSetMetaData.getColumnName(i); + ColumnMeta col = tmeta.getColumnMeta(colName); + int dataType = col.getDataType(); + Field field = new Field(); + field.setName(col.getColumnName()); + if (tmeta.getPrimaryKeyMap().containsKey(colName)) { + field.setKeyType(KeyType.PRIMARY_KEY); + } + field.setType(dataType); + // mysql will not run in this code + // cause mysql does not use java.sql.Blob, java.sql.sql.Clob to process Blob and Clob column + if (dataType == Types.BLOB) { + Blob blob = resultSet.getBlob(i); + if (blob != null) { + field.setValue(new SerialBlob(blob)); + } + } else if (dataType == Types.CLOB) { + Clob clob = resultSet.getClob(i); + if (clob != null) { + field.setValue(new SerialClob(clob)); + } + } else if (dataType == Types.NCLOB) { + NClob object = resultSet.getNClob(i); + if (object != null) { + field.setValue(new SerialClob(object)); + } + } else if (dataType == Types.ARRAY) { + Array array = resultSet.getArray(i); + if (array != null) { + field.setValue(new SerialArray(array)); + } + } else if (dataType == Types.REF) { + Ref ref = resultSet.getRef(i); + if (ref != null) { + field.setValue(new SerialRef(ref)); + } + } else if (dataType == Types.DATALINK) { + java.net.URL url = resultSet.getURL(i); + if (url != null) { + field.setValue(new SerialDatalink(url)); + } + } else if (dataType == Types.JAVA_OBJECT) { + Object object = resultSet.getObject(i); + if (object != null) { + field.setValue(new SerialJavaObject(object)); + } + } else { + // JDBCType.DISTINCT, JDBCType.STRUCT etc... + field.setValue(resultSet.getObject(i)); + } + + fields.add(field); + } + + Row row = new Row(); + row.setFields(fields); + + records.add(row); + } + return records; + } + + public static class EmptyTableRecords extends TableRecords { + + public EmptyTableRecords() {} + + public EmptyTableRecords(TableMeta tableMeta) { + this.setTableMeta(tableMeta); + } + + @Override + public int size() { + return 0; + } + + @Override + public List> pkRows() { + return new ArrayList<>(); + } + + @Override + public void add(Row row) { + throw new UnsupportedOperationException("xxx"); + } + + @Override + public TableMeta getTableMeta() { + throw new UnsupportedOperationException("xxx"); + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java new file mode 100644 index 0000000..dccd807 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/AbstractTableMetaCache.java @@ -0,0 +1,114 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Table meta cache. + * + * @author sharajava + */ +public abstract class AbstractTableMetaCache implements TableMetaCache { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTableMetaCache.class); + + private static final long CACHE_SIZE = 100000; + + private static final long EXPIRE_TIME = 900 * 1000; + + private static final Cache TABLE_META_CACHE = Caffeine.newBuilder().maximumSize(CACHE_SIZE) + .expireAfterWrite(EXPIRE_TIME, TimeUnit.MILLISECONDS).softValues().build(); + + + @Override + public TableMeta getTableMeta(final Connection connection, final String tableName, String resourceId) { + if (StringUtils.isNullOrEmpty(tableName)) { + throw new IllegalArgumentException("TableMeta cannot be fetched without tableName"); + } + + TableMeta tmeta; + final String key = getCacheKey(connection, tableName, resourceId); + tmeta = TABLE_META_CACHE.get(key, mappingFunction -> { + try { + return fetchSchema(connection, tableName); + } catch (SQLException e) { + LOGGER.error("get table meta of the table `{}` error: {}", tableName, e.getMessage(), e); + return null; + } + }); + + if (tmeta == null) { + throw new ShouldNeverHappenException(String.format("[xid:%s]get table meta failed," + + " please check whether the table `%s` exists.", RootContext.getXID(), tableName)); + } + return tmeta; + } + + @Override + public void refresh(final Connection connection, String resourceId) { + ConcurrentMap tableMetaMap = TABLE_META_CACHE.asMap(); + for (Map.Entry entry : tableMetaMap.entrySet()) { + String key = getCacheKey(connection, entry.getValue().getTableName(), resourceId); + if (entry.getKey().equals(key)) { + try { + TableMeta tableMeta = fetchSchema(connection, entry.getValue().getTableName()); + if (!tableMeta.equals(entry.getValue())) { + TABLE_META_CACHE.put(entry.getKey(), tableMeta); + LOGGER.info("table meta change was found, update table meta cache automatically."); + } + } catch (SQLException e) { + LOGGER.error("get table meta error:{}", e.getMessage(), e); + } + } + } + } + + /** + * generate cache key + * + * @param connection the connection + * @param tableName the table name + * @param resourceId the resource id + * @return cache key + */ + protected abstract String getCacheKey(Connection connection, String tableName, String resourceId); + + /** + * get scheme from datasource and tableName + * + * @param connection the connection + * @param tableName the table name + * @return table meta + * @throws SQLException the sql exception + */ + protected abstract TableMeta fetchSchema(Connection connection, String tableName) throws SQLException; + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCache.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCache.java new file mode 100644 index 0000000..9adcb85 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCache.java @@ -0,0 +1,178 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.IndexMeta; +import io.seata.rm.datasource.sql.struct.IndexType; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Table meta cache. + * + * @author sharajava + */ +@LoadLevel(name = JdbcConstants.MYSQL) +public class MysqlTableMetaCache extends AbstractTableMetaCache { + + private static final Logger LOGGER = LoggerFactory.getLogger(MysqlTableMetaCache.class); + + @Override + protected String getCacheKey(Connection connection, String tableName, String resourceId) { + StringBuilder cacheKey = new StringBuilder(resourceId); + cacheKey.append("."); + //remove single quote and separate it to catalogName and tableName + String[] tableNameWithCatalog = tableName.replace("`", "").split("\\."); + String defaultTableName = tableNameWithCatalog.length > 1 ? tableNameWithCatalog[1] : tableNameWithCatalog[0]; + + DatabaseMetaData databaseMetaData = null; + try { + databaseMetaData = connection.getMetaData(); + } catch (SQLException e) { + LOGGER.error("Could not get connection, use default cache key {}", e.getMessage(), e); + return cacheKey.append(defaultTableName).toString(); + } + + try { + //prevent duplicated cache key + if (databaseMetaData.supportsMixedCaseIdentifiers()) { + cacheKey.append(defaultTableName); + } else { + cacheKey.append(defaultTableName.toLowerCase()); + } + } catch (SQLException e) { + LOGGER.error("Could not get supportsMixedCaseIdentifiers in connection metadata, use default cache key {}", e.getMessage(), e); + return cacheKey.append(defaultTableName).toString(); + } + + return cacheKey.toString(); + } + + @Override + protected TableMeta fetchSchema(Connection connection, String tableName) throws SQLException { + String sql = "SELECT * FROM " + ColumnUtils.addEscape(tableName, JdbcConstants.MYSQL) + " LIMIT 1"; + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + return resultSetMetaToSchema(rs.getMetaData(), connection.getMetaData()); + } catch (SQLException sqlEx) { + throw sqlEx; + } catch (Exception e) { + throw new SQLException(String.format("Failed to fetch schema of %s", tableName), e); + } + } + + private TableMeta resultSetMetaToSchema(ResultSetMetaData rsmd, DatabaseMetaData dbmd) + throws SQLException { + //always "" for mysql + String schemaName = rsmd.getSchemaName(1); + String catalogName = rsmd.getCatalogName(1); + /* + * use ResultSetMetaData to get the pure table name + * can avoid the problem below + * + * select * from account_tbl + * select * from account_TBL + * select * from `account_tbl` + * select * from account.account_tbl + */ + String tableName = rsmd.getTableName(1); + + TableMeta tm = new TableMeta(); + tm.setTableName(tableName); + + /* + * here has two different type to get the data + * make sure the table name was right + * 1. show full columns from xxx from xxx(normal) + * 2. select xxx from xxx where catalog_name like ? and table_name like ?(informationSchema=true) + */ + + try (ResultSet rsColumns = dbmd.getColumns(catalogName, schemaName, tableName, "%"); + ResultSet rsIndex = dbmd.getIndexInfo(catalogName, schemaName, tableName, false, true)) { + while (rsColumns.next()) { + ColumnMeta col = new ColumnMeta(); + col.setTableCat(rsColumns.getString("TABLE_CAT")); + col.setTableSchemaName(rsColumns.getString("TABLE_SCHEM")); + col.setTableName(rsColumns.getString("TABLE_NAME")); + col.setColumnName(rsColumns.getString("COLUMN_NAME")); + col.setDataType(rsColumns.getInt("DATA_TYPE")); + col.setDataTypeName(rsColumns.getString("TYPE_NAME")); + col.setColumnSize(rsColumns.getInt("COLUMN_SIZE")); + col.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS")); + col.setNumPrecRadix(rsColumns.getInt("NUM_PREC_RADIX")); + col.setNullAble(rsColumns.getInt("NULLABLE")); + col.setRemarks(rsColumns.getString("REMARKS")); + col.setColumnDef(rsColumns.getString("COLUMN_DEF")); + col.setSqlDataType(rsColumns.getInt("SQL_DATA_TYPE")); + col.setSqlDatetimeSub(rsColumns.getInt("SQL_DATETIME_SUB")); + col.setCharOctetLength(rsColumns.getInt("CHAR_OCTET_LENGTH")); + col.setOrdinalPosition(rsColumns.getInt("ORDINAL_POSITION")); + col.setIsNullAble(rsColumns.getString("IS_NULLABLE")); + col.setIsAutoincrement(rsColumns.getString("IS_AUTOINCREMENT")); + + tm.getAllColumns().put(col.getColumnName(), col); + } + + while (rsIndex.next()) { + String indexName = rsIndex.getString("INDEX_NAME"); + String colName = rsIndex.getString("COLUMN_NAME"); + ColumnMeta col = tm.getAllColumns().get(colName); + + if (tm.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tm.getAllIndexes().get(indexName); + index.getValues().add(col); + } else { + IndexMeta index = new IndexMeta(); + index.setIndexName(indexName); + index.setNonUnique(rsIndex.getBoolean("NON_UNIQUE")); + index.setIndexQualifier(rsIndex.getString("INDEX_QUALIFIER")); + index.setIndexName(rsIndex.getString("INDEX_NAME")); + index.setType(rsIndex.getShort("TYPE")); + index.setOrdinalPosition(rsIndex.getShort("ORDINAL_POSITION")); + index.setAscOrDesc(rsIndex.getString("ASC_OR_DESC")); + index.setCardinality(rsIndex.getInt("CARDINALITY")); + index.getValues().add(col); + if ("PRIMARY".equalsIgnoreCase(indexName)) { + index.setIndextype(IndexType.PRIMARY); + } else if (!index.isNonUnique()) { + index.setIndextype(IndexType.UNIQUE); + } else { + index.setIndextype(IndexType.NORMAL); + } + tm.getAllIndexes().put(indexName, index); + + } + } + if (tm.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException("Could not found any index in the table: " + tableName); + } + } + return tm; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCache.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCache.java new file mode 100644 index 0000000..6f8512c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCache.java @@ -0,0 +1,161 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.IndexMeta; +import io.seata.rm.datasource.sql.struct.IndexType; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type Table meta cache. + * + * @author ygy + */ +@LoadLevel(name = JdbcConstants.ORACLE) +public class OracleTableMetaCache extends AbstractTableMetaCache { + + @Override + protected String getCacheKey(Connection connection, String tableName, String resourceId) { + StringBuilder cacheKey = new StringBuilder(resourceId); + cacheKey.append("."); + + //separate it to schemaName and tableName + String[] tableNameWithSchema = tableName.split("\\."); + String defaultTableName = tableNameWithSchema.length > 1 ? tableNameWithSchema[1] : tableNameWithSchema[0]; + + //oracle does not implement supportsMixedCaseIdentifiers in DatabaseMetadata + if (defaultTableName.contains("\"")) { + cacheKey.append(defaultTableName.replace("\"", "")); + } else { + // oracle default store in upper case + cacheKey.append(defaultTableName.toUpperCase()); + } + + return cacheKey.toString(); + } + + @Override + protected TableMeta fetchSchema(Connection connection, String tableName) throws SQLException { + try { + return resultSetMetaToSchema(connection.getMetaData(), tableName); + } catch (SQLException sqlEx) { + throw sqlEx; + } catch (Exception e) { + throw new SQLException(String.format("Failed to fetch schema of %s", tableName), e); + } + } + + private TableMeta resultSetMetaToSchema(DatabaseMetaData dbmd, String tableName) throws SQLException { + TableMeta tm = new TableMeta(); + tm.setTableName(tableName); + String[] schemaTable = tableName.split("\\."); + String schemaName = schemaTable.length > 1 ? schemaTable[0] : dbmd.getUserName(); + tableName = schemaTable.length > 1 ? schemaTable[1] : tableName; + if (schemaName.contains("\"")) { + schemaName = schemaName.replace("\"", ""); + } else { + schemaName = schemaName.toUpperCase(); + } + + if (tableName.contains("\"")) { + tableName = tableName.replace("\"", ""); + + } else { + tableName = tableName.toUpperCase(); + } + + try (ResultSet rsColumns = dbmd.getColumns("", schemaName, tableName, "%"); + ResultSet rsIndex = dbmd.getIndexInfo(null, schemaName, tableName, false, true); + ResultSet rsPrimary = dbmd.getPrimaryKeys(null, schemaName, tableName)) { + while (rsColumns.next()) { + ColumnMeta col = new ColumnMeta(); + col.setTableCat(rsColumns.getString("TABLE_CAT")); + col.setTableSchemaName(rsColumns.getString("TABLE_SCHEM")); + col.setTableName(rsColumns.getString("TABLE_NAME")); + col.setColumnName(rsColumns.getString("COLUMN_NAME")); + col.setDataType(rsColumns.getInt("DATA_TYPE")); + col.setDataTypeName(rsColumns.getString("TYPE_NAME")); + col.setColumnSize(rsColumns.getInt("COLUMN_SIZE")); + col.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS")); + col.setNumPrecRadix(rsColumns.getInt("NUM_PREC_RADIX")); + col.setNullAble(rsColumns.getInt("NULLABLE")); + col.setRemarks(rsColumns.getString("REMARKS")); + col.setColumnDef(rsColumns.getString("COLUMN_DEF")); + col.setSqlDataType(rsColumns.getInt("SQL_DATA_TYPE")); + col.setSqlDatetimeSub(rsColumns.getInt("SQL_DATETIME_SUB")); + col.setCharOctetLength(rsColumns.getInt("CHAR_OCTET_LENGTH")); + col.setOrdinalPosition(rsColumns.getInt("ORDINAL_POSITION")); + col.setIsNullAble(rsColumns.getString("IS_NULLABLE")); + + tm.getAllColumns().put(col.getColumnName(), col); + } + + while (rsIndex.next()) { + String indexName = rsIndex.getString("INDEX_NAME"); + if (StringUtils.isNullOrEmpty(indexName)) { + continue; + } + String colName = rsIndex.getString("COLUMN_NAME"); + ColumnMeta col = tm.getAllColumns().get(colName); + if (tm.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tm.getAllIndexes().get(indexName); + index.getValues().add(col); + } else { + IndexMeta index = new IndexMeta(); + index.setIndexName(indexName); + index.setNonUnique(rsIndex.getBoolean("NON_UNIQUE")); + index.setIndexQualifier(rsIndex.getString("INDEX_QUALIFIER")); + index.setIndexName(rsIndex.getString("INDEX_NAME")); + index.setType(rsIndex.getShort("TYPE")); + index.setOrdinalPosition(rsIndex.getShort("ORDINAL_POSITION")); + index.setAscOrDesc(rsIndex.getString("ASC_OR_DESC")); + index.setCardinality(rsIndex.getInt("CARDINALITY")); + index.getValues().add(col); + if (!index.isNonUnique()) { + index.setIndextype(IndexType.UNIQUE); + } else { + index.setIndextype(IndexType.NORMAL); + } + tm.getAllIndexes().put(indexName, index); + + } + } + + while (rsPrimary.next()) { + String pkIndexName = rsPrimary.getString("PK_NAME"); + if (tm.getAllIndexes().containsKey(pkIndexName)) { + IndexMeta index = tm.getAllIndexes().get(pkIndexName); + index.setIndextype(IndexType.PRIMARY); + } + } + if (tm.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException(String.format("Could not found any index in the table: %s", tableName)); + } + } + + return tm; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCache.java b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCache.java new file mode 100644 index 0000000..73c8be0 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCache.java @@ -0,0 +1,178 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.StringUtils; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.IndexMeta; +import io.seata.rm.datasource.sql.struct.IndexType; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type Table meta cache. + * + * @author jaspercloud + */ +@LoadLevel(name = JdbcConstants.POSTGRESQL) +public class PostgresqlTableMetaCache extends AbstractTableMetaCache { + + @Override + protected String getCacheKey(Connection connection, String tableName, String resourceId) { + StringBuilder cacheKey = new StringBuilder(resourceId); + cacheKey.append("."); + + //separate it to schemaName and tableName + String[] tableNameWithSchema = tableName.split("\\."); + String defaultTableName = tableNameWithSchema.length > 1 ? tableNameWithSchema[1] : tableNameWithSchema[0]; + + //postgres does not implement supportsMixedCaseIdentifiers in DatabaseMetadata + if (defaultTableName.contains("\"")) { + cacheKey.append(defaultTableName.replace("\"", "")); + } else { + //postgres default store in lower case + cacheKey.append(defaultTableName.toLowerCase()); + } + + return cacheKey.toString(); + } + + @Override + protected TableMeta fetchSchema(Connection connection, String tableName) throws SQLException { + try { + DatabaseMetaData dbmd = connection.getMetaData(); + return resultSetMetaToSchema(dbmd, tableName); + } catch (SQLException sqlEx) { + throw sqlEx; + } catch (Exception e) { + throw new SQLException("Failed to fetch schema of " + tableName, e); + } + } + + private TableMeta resultSetMetaToSchema(DatabaseMetaData dbmd, String tableName) throws SQLException { + TableMeta tm = new TableMeta(); + tm.setTableName(tableName); + String[] schemaTable = tableName.split("\\."); + String schemaName = schemaTable.length > 1 ? schemaTable[0] : null; + tableName = schemaTable.length > 1 ? schemaTable[1] : tableName; + /* + * use ResultSetMetaData to get the pure table name + * can avoid the problem below + * + * select * from account_tbl + * select * from account_TBL + * select * from account_tbl + * select * from account.account_tbl + * select * from "select" + * select * from "Select" + * select * from "Sel""ect" + * select * from "Sel'ect" + * select * from TEST.test + * select * from test.TEST + * select * from "Test".test + * select * from "Test"."Select" + */ + if (schemaName != null) { + if (schemaName.startsWith("\"") && schemaName.endsWith("\"")) { + schemaName = schemaName.replaceAll("(^\")|(\"$)", ""); + } else { + schemaName = schemaName.toLowerCase(); + } + } + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + tableName = tableName.replaceAll("(^\")|(\"$)", ""); + } else { + tableName = tableName.toLowerCase(); + } + + try (ResultSet rsColumns = dbmd.getColumns(null, schemaName, tableName, "%"); + ResultSet rsIndex = dbmd.getIndexInfo(null, schemaName, tableName, false, true); + ResultSet rsPrimary = dbmd.getPrimaryKeys(null, schemaName, tableName)) { + while (rsColumns.next()) { + ColumnMeta col = new ColumnMeta(); + col.setTableCat(rsColumns.getString("TABLE_CAT")); + col.setTableSchemaName(rsColumns.getString("TABLE_SCHEM")); + col.setTableName(rsColumns.getString("TABLE_NAME")); + col.setColumnName(rsColumns.getString("COLUMN_NAME")); + col.setDataType(rsColumns.getInt("DATA_TYPE")); + col.setDataTypeName(rsColumns.getString("TYPE_NAME")); + col.setColumnSize(rsColumns.getInt("COLUMN_SIZE")); + col.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS")); + col.setNumPrecRadix(rsColumns.getInt("NUM_PREC_RADIX")); + col.setNullAble(rsColumns.getInt("NULLABLE")); + col.setRemarks(rsColumns.getString("REMARKS")); + col.setColumnDef(rsColumns.getString("COLUMN_DEF")); + col.setSqlDataType(rsColumns.getInt("SQL_DATA_TYPE")); + col.setSqlDatetimeSub(rsColumns.getInt("SQL_DATETIME_SUB")); + col.setCharOctetLength(rsColumns.getObject("CHAR_OCTET_LENGTH")); + col.setOrdinalPosition(rsColumns.getInt("ORDINAL_POSITION")); + col.setIsNullAble(rsColumns.getString("IS_NULLABLE")); + col.setIsAutoincrement(rsColumns.getString("IS_AUTOINCREMENT")); + tm.getAllColumns().put(col.getColumnName(), col); + } + + while (rsIndex.next()) { + String indexName = rsIndex.getString("index_name"); + if (StringUtils.isNullOrEmpty(indexName)) { + continue; + } + String colName = rsIndex.getString("column_name"); + ColumnMeta col = tm.getAllColumns().get(colName); + if (tm.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tm.getAllIndexes().get(indexName); + index.getValues().add(col); + } else { + IndexMeta index = new IndexMeta(); + index.setIndexName(indexName); + index.setNonUnique(rsIndex.getBoolean("non_unique")); + index.setIndexQualifier(rsIndex.getString("index_qualifier")); + index.setIndexName(rsIndex.getString("index_name")); + index.setType(rsIndex.getShort("type")); + index.setOrdinalPosition(rsIndex.getShort("ordinal_position")); + index.setAscOrDesc(rsIndex.getString("asc_or_desc")); + index.setCardinality(rsIndex.getInt("cardinality")); + index.getValues().add(col); + if (!index.isNonUnique()) { + index.setIndextype(IndexType.UNIQUE); + } else { + index.setIndextype(IndexType.NORMAL); + } + tm.getAllIndexes().put(indexName, index); + + } + } + + while (rsPrimary.next()) { + String pkIndexName = rsPrimary.getString("pk_name"); + if (tm.getAllIndexes().containsKey(pkIndexName)) { + IndexMeta index = tm.getAllIndexes().get(pkIndexName); + index.setIndextype(IndexType.PRIMARY); + } + } + if (tm.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException("Could not found any index in the table: " + tableName); + } + } + + return tm; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoExecutor.java new file mode 100644 index 0000000..36c423a --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoExecutor.java @@ -0,0 +1,387 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import javax.sql.rowset.serial.SerialDatalink; +import java.sql.Array; +import java.sql.Connection; +import java.sql.JDBCType; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson.JSON; +import io.seata.common.util.BlobUtils; +import io.seata.common.util.IOUtil; +import io.seata.common.util.StringUtils; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.model.Result; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.DataCompareUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.serial.SerialArray; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.util.JdbcUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.seata.common.DefaultValues.DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * The type Abstract undo executor. + * + * @author sharajava + * @author Geng Zhang + */ +public abstract class AbstractUndoExecutor { + + /** + * Logger for AbstractUndoExecutor + **/ + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractUndoExecutor.class); + + /** + * template of check sql + * TODO support multiple primary key + */ + private static final String CHECK_SQL_TEMPLATE = "SELECT * FROM %s WHERE %s FOR UPDATE"; + + /** + * Switch of undo data validation + */ + public static final boolean IS_UNDO_DATA_VALIDATION_ENABLE = ConfigurationFactory.getInstance() + .getBoolean(ConfigurationKeys.TRANSACTION_UNDO_DATA_VALIDATION, DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION); + + /** + * The Sql undo log. + */ + protected SQLUndoLog sqlUndoLog; + + /** + * Build undo sql string. + * + * @return the string + */ + protected abstract String buildUndoSQL(); + + /** + * Instantiates a new Abstract undo executor. + * + * @param sqlUndoLog the sql undo log + */ + public AbstractUndoExecutor(SQLUndoLog sqlUndoLog) { + this.sqlUndoLog = sqlUndoLog; + } + + /** + * Gets sql undo log. + * + * @return the sql undo log + */ + public SQLUndoLog getSqlUndoLog() { + return sqlUndoLog; + } + + /** + * Execute on. + * + * @param conn the conn + * @throws SQLException the sql exception + */ + public void executeOn(Connection conn) throws SQLException { + if (IS_UNDO_DATA_VALIDATION_ENABLE && !dataValidationAndGoOn(conn)) { + return; + } + try { + String undoSQL = buildUndoSQL(); + PreparedStatement undoPST = conn.prepareStatement(undoSQL); + TableRecords undoRows = getUndoRows(); + for (Row undoRow : undoRows.getRows()) { + ArrayList undoValues = new ArrayList<>(); + List pkValueList = getOrderedPkList(undoRows, undoRow, getDbType(conn)); + for (Field field : undoRow.getFields()) { + if (field.getKeyType() != KeyType.PRIMARY_KEY) { + undoValues.add(field); + } + } + + undoPrepare(undoPST, undoValues, pkValueList); + + undoPST.executeUpdate(); + } + + } catch (Exception ex) { + if (ex instanceof SQLException) { + throw (SQLException) ex; + } else { + throw new SQLException(ex); + } + } + + } + + /** + * Undo prepare. + * + * @param undoPST the undo pst + * @param undoValues the undo values + * @param pkValueList the pk value + * @throws SQLException the sql exception + */ + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) + throws SQLException { + int undoIndex = 0; + for (Field undoValue : undoValues) { + undoIndex++; + int type = undoValue.getType(); + Object value = undoValue.getValue(); + if (type == JDBCType.BLOB.getVendorTypeNumber()) { + SerialBlob serialBlob = (SerialBlob) value; + if (serialBlob != null) { + undoPST.setBytes(undoIndex, BlobUtils.blob2Bytes(serialBlob)); + } else { + undoPST.setObject(undoIndex, null); + } + } else if (type == JDBCType.CLOB.getVendorTypeNumber()) { + SerialClob serialClob = (SerialClob) value; + if (serialClob != null) { + undoPST.setClob(undoIndex, serialClob.getCharacterStream()); + } else { + undoPST.setObject(undoIndex, null); + } + } else if (type == JDBCType.DATALINK.getVendorTypeNumber()) { + SerialDatalink dataLink = (SerialDatalink) value; + if (dataLink != null) { + undoPST.setURL(undoIndex, dataLink.getDatalink()); + } else { + undoPST.setObject(undoIndex, null); + } + } else if (type == JDBCType.ARRAY.getVendorTypeNumber()) { + SerialArray array = (SerialArray) value; + if (array != null) { + Array arrayOf = undoPST.getConnection().createArrayOf(array.getBaseTypeName(), array.getElements()); + undoPST.setArray(undoIndex, arrayOf); + } else { + undoPST.setObject(undoIndex, null); + } + } else if (undoValue.getType() == JDBCType.OTHER.getVendorTypeNumber()) { + undoPST.setObject(undoIndex, value); + } else { + // JDBCType.REF, JDBCType.JAVA_OBJECT etc... + undoPST.setObject(undoIndex, value, type); + } + } + // PK is always at last. + // INSERT INTO a (x, y, z, pk1,pk2) VALUES (?, ?, ?, ? ,?) + // UPDATE a SET x=?, y=?, z=? WHERE pk1 in (?) and pk2 in (?) + // DELETE FROM a WHERE pk1 in (?) and pk2 in (?) + for (Field pkField : pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + + } + + /** + * Gets undo rows. + * + * @return the undo rows + */ + protected abstract TableRecords getUndoRows(); + + /** + * Data validation. + * + * @param conn the conn + * @return return true if data validation is ok and need continue undo, and return false if no need continue undo. + * @throws SQLException the sql exception such as has dirty data + */ + protected boolean dataValidationAndGoOn(Connection conn) throws SQLException { + + TableRecords beforeRecords = sqlUndoLog.getBeforeImage(); + TableRecords afterRecords = sqlUndoLog.getAfterImage(); + + // Compare current data with before data + // No need undo if the before data snapshot is equivalent to the after data snapshot. + Result beforeEqualsAfterResult = DataCompareUtils.isRecordsEquals(beforeRecords, afterRecords); + if (beforeEqualsAfterResult.getResult()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Stop rollback because there is no data change " + + "between the before data snapshot and the after data snapshot."); + } + // no need continue undo. + return false; + } + + // Validate if data is dirty. + TableRecords currentRecords = queryCurrentRecords(conn); + // compare with current data and after image. + Result afterEqualsCurrentResult = DataCompareUtils.isRecordsEquals(afterRecords, currentRecords); + if (!afterEqualsCurrentResult.getResult()) { + + // If current data is not equivalent to the after data, then compare the current data with the before + // data, too. No need continue to undo if current data is equivalent to the before data snapshot + Result beforeEqualsCurrentResult = DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords); + if (beforeEqualsCurrentResult.getResult()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Stop rollback because there is no data change " + + "between the before data snapshot and the current data snapshot."); + } + // no need continue undo. + return false; + } else { + if (LOGGER.isInfoEnabled()) { + if (StringUtils.isNotBlank(afterEqualsCurrentResult.getErrMsg())) { + LOGGER.info(afterEqualsCurrentResult.getErrMsg(), afterEqualsCurrentResult.getErrMsgParams()); + } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("check dirty datas failed, old and new data are not equal," + + "tableName:[" + sqlUndoLog.getTableName() + "]," + + "oldRows:[" + JSON.toJSONString(afterRecords.getRows()) + "]," + + "newRows:[" + JSON.toJSONString(currentRecords.getRows()) + "]."); + } + throw new SQLException("Has dirty records when undo."); + } + } + return true; + } + + /** + * Query current records. + * + * @param conn the conn + * @return the table records + * @throws SQLException the sql exception + */ + protected TableRecords queryCurrentRecords(Connection conn) throws SQLException { + TableRecords undoRecords = getUndoRows(); + TableMeta tableMeta = undoRecords.getTableMeta(); + //the order of element matters + List pkNameList = tableMeta.getPrimaryKeyOnlyName(); + + // pares pk values + Map> pkRowValues = parsePkValues(getUndoRows()); + if (pkRowValues.size() == 0) { + return TableRecords.empty(tableMeta); + } + // build check sql + String firstKey = pkRowValues.keySet().stream().findFirst().get(); + int pkRowSize = pkRowValues.get(firstKey).size(); + String checkSQL = String.format(CHECK_SQL_TEMPLATE, sqlUndoLog.getTableName(), + SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, pkRowSize, getDbType(conn))); + + PreparedStatement statement = null; + ResultSet checkSet = null; + TableRecords currentRecords; + try { + statement = conn.prepareStatement(checkSQL); + int paramIndex = 1; + int rowSize = pkRowValues.get(pkNameList.get(0)).size(); + for (int r = 0; r < rowSize; r++) { + for (int c = 0; c < pkNameList.size(); c++) { + List pkColumnValueList = pkRowValues.get(pkNameList.get(c)); + Field field = pkColumnValueList.get(r); + int dataType = tableMeta.getColumnMeta(field.getName()).getDataType(); + statement.setObject(paramIndex, field.getValue(), dataType); + paramIndex++; + } + } + + checkSet = statement.executeQuery(); + currentRecords = TableRecords.buildRecords(tableMeta, checkSet); + } finally { + IOUtil.close(checkSet, statement); + } + return currentRecords; + } + + protected List getOrderedPkList(TableRecords image, Row row, String dbType) { + List pkFields = new ArrayList<>(); + // To ensure the order of the pk, the order should based on getPrimaryKeyOnlyName. + List pkColumnNameListByOrder = image.getTableMeta().getPrimaryKeyOnlyName(); + List pkColumnNameListNoOrder = row.primaryKeys() + .stream() + .map(e -> ColumnUtils.delEscape(e.getName(), dbType)) + .collect(Collectors.toList()); + pkColumnNameListByOrder.forEach(pkName -> { + int pkIndex = pkColumnNameListNoOrder.indexOf(pkName); + if (pkIndex != -1) { + // add PK to the last of the list. + pkFields.add(row.primaryKeys().get(pkIndex)); + } + }); + return pkFields; + } + + + /** + * Parse pk values Field List. + * + * @param records the records + * @return each element represents a row. And inside a row list contains pk columns(Field). + */ + protected Map> parsePkValues(TableRecords records) { + return parsePkValues(records.getRows(), records.getTableMeta().getPrimaryKeyOnlyName()); + } + + /** + * Parse pk values Field List. + * + * @param rows pk rows + * @param pkNameList pk column name + * @return each element represents a row. And inside a row list contains pk columns(Field). + */ + protected Map> parsePkValues(List rows, List pkNameList) { + List pkFieldList = new ArrayList<>(); + for (int i = 0; i < rows.size(); i++) { + List fields = rows.get(i).getFields(); + if (fields != null) { + for (Field field : fields) { + if (pkNameList.stream().anyMatch(e -> field.getName().equalsIgnoreCase(e))) { + pkFieldList.add(field); + } + } + } + } + Map> pkValueMap = pkFieldList.stream().collect(Collectors.groupingBy(Field::getName)); + return pkValueMap; + } + + /** + * Get db type + * + * @param conn the connection + * @return the db type + * @throws SQLException SQLException + */ + protected String getDbType(Connection conn) throws SQLException { + return JdbcUtils.getDbType(conn.getMetaData().getURL()); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoLogManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoLogManager.java new file mode 100644 index 0000000..de8db3b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/AbstractUndoLogManager.java @@ -0,0 +1,443 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLIntegrityConstraintViolationException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.seata.common.Constants; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.SizeUtil; +import io.seata.config.ConfigurationFactory; +import io.seata.core.compressor.CompressorFactory; +import io.seata.core.compressor.CompressorType; +import io.seata.core.constants.ClientTableColumnsName; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.exception.BranchTransactionException; +import io.seata.core.exception.TransactionException; +import io.seata.rm.datasource.ConnectionContext; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.extend.FtbSeataExtendManagerHolder; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.swing.*; + +import static io.seata.common.DefaultValues.DEFAULT_TRANSACTION_UNDO_LOG_TABLE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_UNDO_COMPRESS_ENABLE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_UNDO_COMPRESS_TYPE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD; +import static io.seata.core.exception.TransactionExceptionCode.BranchRollbackFailed_Retriable; + +/** + * @author jsbxyyx + */ +public abstract class AbstractUndoLogManager implements UndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractUndoLogManager.class); + + protected enum State { + /** + * This state can be properly rolled back by services + */ + Normal(0), + /** + * This state prevents the branch transaction from inserting undo_log after the global transaction is rolled + * back. + */ + GlobalFinished(1); + + private int value; + + State(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + protected static final String UNDO_LOG_TABLE_NAME = ConfigurationFactory.getInstance().getConfig( + ConfigurationKeys.TRANSACTION_UNDO_LOG_TABLE, DEFAULT_TRANSACTION_UNDO_LOG_TABLE); + + protected static final String SELECT_UNDO_LOG_SQL = "SELECT * FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + " = ? AND " + ClientTableColumnsName.UNDO_LOG_XID + + " = ? FOR UPDATE"; + + protected static final String DELETE_UNDO_LOG_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + " = ? AND " + ClientTableColumnsName.UNDO_LOG_XID + " = ?"; + + protected static final boolean ROLLBACK_INFO_COMPRESS_ENABLE = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.CLIENT_UNDO_COMPRESS_ENABLE, DEFAULT_CLIENT_UNDO_COMPRESS_ENABLE); + + protected static final CompressorType ROLLBACK_INFO_COMPRESS_TYPE = CompressorType.getByName(ConfigurationFactory.getInstance().getConfig( + ConfigurationKeys.CLIENT_UNDO_COMPRESS_TYPE, DEFAULT_CLIENT_UNDO_COMPRESS_TYPE)); + + protected static final long ROLLBACK_INFO_COMPRESS_THRESHOLD = SizeUtil.size2Long(ConfigurationFactory.getInstance().getConfig( + ConfigurationKeys.CLIENT_UNDO_COMPRESS_THRESHOLD, DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD)); + + private static final ThreadLocal SERIALIZER_LOCAL = new ThreadLocal<>(); + + public static String getCurrentSerializer() { + return SERIALIZER_LOCAL.get(); + } + + public static void setCurrentSerializer(String serializer) { + SERIALIZER_LOCAL.set(serializer); + } + + public static void removeCurrentSerializer() { + SERIALIZER_LOCAL.remove(); + } + + /** + * Delete undo log. + * + * @param xid the xid + * @param branchId the branch id + * @param conn the conn + * @throws SQLException the sql exception + */ + @Override + public void deleteUndoLog(String xid, long branchId, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_SQL)) { + deletePST.setLong(1, branchId); + deletePST.setString(2, xid); + deletePST.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + /** + * batch Delete undo log. + * + * @param xids xid + * @param branchIds branch Id + * @param conn connection + */ + @Override + public void batchDeleteUndoLog(Set xids, Set branchIds, Connection conn) throws SQLException { + if (CollectionUtils.isEmpty(xids) || CollectionUtils.isEmpty(branchIds)) { + return; + } + int xidSize = xids.size(); + int branchIdSize = branchIds.size(); + String batchDeleteSql = toBatchDeleteUndoLogSql(xidSize, branchIdSize); + try (PreparedStatement deletePST = conn.prepareStatement(batchDeleteSql)) { + int paramsIndex = 1; + for (Long branchId : branchIds) { + deletePST.setLong(paramsIndex++, branchId); + } + for (String xid : xids) { + // seata自定义扩展,处理connection连接变更 + FtbSeataExtendManagerHolder.get().undoLogConnection(xid,conn); + deletePST.setString(paramsIndex++, xid); + } + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + protected static String toBatchDeleteUndoLogSql(int xidSize, int branchIdSize) { + StringBuilder sqlBuilder = new StringBuilder(64); + sqlBuilder.append("DELETE FROM ").append(UNDO_LOG_TABLE_NAME).append(" WHERE ").append( + ClientTableColumnsName.UNDO_LOG_BRANCH_XID).append(" IN "); + appendInParam(branchIdSize, sqlBuilder); + sqlBuilder.append(" AND ").append(ClientTableColumnsName.UNDO_LOG_XID).append(" IN "); + appendInParam(xidSize, sqlBuilder); + return sqlBuilder.toString(); + } + + protected static void appendInParam(int size, StringBuilder sqlBuilder) { + sqlBuilder.append(" ("); + for (int i = 0; i < size; i++) { + sqlBuilder.append("?"); + if (i < (size - 1)) { + sqlBuilder.append(","); + } + } + sqlBuilder.append(") "); + } + + protected static boolean canUndo(int state) { + return state == State.Normal.getValue(); + } + + protected String buildContext(String serializer, CompressorType compressorType) { + Map map = new HashMap<>(); + map.put(UndoLogConstants.SERIALIZER_KEY, serializer); + map.put(UndoLogConstants.COMPRESSOR_TYPE_KEY, compressorType.name()); + return CollectionUtils.encodeMap(map); + } + + protected Map parseContext(String data) { + return CollectionUtils.decodeMap(data); + } + + /** + * Flush undo logs. + * + * @param cp the cp + * @throws SQLException the sql exception + */ + @Override + public void flushUndoLogs(ConnectionProxy cp) throws SQLException { + ConnectionContext connectionContext = cp.getContext(); + if (!connectionContext.hasUndoLog()) { + return; + } + + String xid = connectionContext.getXid(); + long branchId = connectionContext.getBranchId(); + // seata自定义扩展,处理connection连接变更 + FtbSeataExtendManagerHolder.get().flushUndoLogsConnection(cp,xid); + + BranchUndoLog branchUndoLog = new BranchUndoLog(); + branchUndoLog.setXid(xid); + branchUndoLog.setBranchId(branchId); + branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems()); + + UndoLogParser parser = UndoLogParserFactory.getInstance(); + byte[] undoLogContent = parser.encode(branchUndoLog); + + CompressorType compressorType = CompressorType.NONE; + if (needCompress(undoLogContent)) { + compressorType = ROLLBACK_INFO_COMPRESS_TYPE; + undoLogContent = CompressorFactory.getCompressor(compressorType.getCode()).compress(undoLogContent); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Flushing UNDO LOG: {}", new String(undoLogContent, Constants.DEFAULT_CHARSET)); + } + + insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName(), compressorType), undoLogContent, cp.getTargetConnection()); + } + + /** + * Undo. + * + * @param dataSourceProxy the data source proxy + * @param xid the xid + * @param branchId the branch id + * @throws TransactionException the transaction exception + */ + @Override + public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException { + Connection conn = null; + ResultSet rs = null; + PreparedStatement selectPST = null; + boolean originalAutoCommit = true; + + for (; ; ) { + try { + conn = dataSourceProxy.getPlainConnection(); + // seata自定义扩展,处理connection连接变更 + FtbSeataExtendManagerHolder.get().undoLogConnection(xid,conn); + + // The entire undo process should run in a local transaction. + if (originalAutoCommit = conn.getAutoCommit()) { + conn.setAutoCommit(false); + } + + // Find UNDO LOG + selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL); + selectPST.setLong(1, branchId); + selectPST.setString(2, xid); + rs = selectPST.executeQuery(); + + boolean exists = false; + while (rs.next()) { + exists = true; + + // It is possible that the server repeatedly sends a rollback request to roll back + // the same branch transaction to multiple processes, + // ensuring that only the undo_log in the normal state is processed. + int state = rs.getInt(ClientTableColumnsName.UNDO_LOG_LOG_STATUS); + if (!canUndo(state)) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("xid {} branch {}, ignore {} undo_log", xid, branchId, state); + } + return; + } + + String contextString = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT); + Map context = parseContext(contextString); + byte[] rollbackInfo = getRollbackInfo(rs); + + String serializer = context == null ? null : context.get(UndoLogConstants.SERIALIZER_KEY); + UndoLogParser parser = serializer == null ? UndoLogParserFactory.getInstance() + : UndoLogParserFactory.getInstance(serializer); + BranchUndoLog branchUndoLog = parser.decode(rollbackInfo); + + try { + // put serializer name to local + setCurrentSerializer(parser.getName()); + List sqlUndoLogs = branchUndoLog.getSqlUndoLogs(); + if (sqlUndoLogs.size() > 1) { + Collections.reverse(sqlUndoLogs); + } + for (SQLUndoLog sqlUndoLog : sqlUndoLogs) { + TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType()).getTableMeta( + conn, sqlUndoLog.getTableName(), dataSourceProxy.getResourceId()); + sqlUndoLog.setTableMeta(tableMeta); + AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor( + dataSourceProxy.getDbType(), sqlUndoLog); + undoExecutor.executeOn(conn); + } + } finally { + // remove serializer name + removeCurrentSerializer(); + } + } + + // If undo_log exists, it means that the branch transaction has completed the first phase, + // we can directly roll back and clean the undo_log + // Otherwise, it indicates that there is an exception in the branch transaction, + // causing undo_log not to be written to the database. + // For example, the business processing timeout, the global transaction is the initiator rolls back. + // To ensure data consistency, we can insert an undo_log with GlobalFinished state + // to prevent the local transaction of the first phase of other programs from being correctly submitted. + // See https://github.com/seata/seata/issues/489 + + if (exists) { + deleteUndoLog(xid, branchId, conn); + conn.commit(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("xid {} branch {}, undo_log deleted with {}", xid, branchId, + State.GlobalFinished.name()); + } + } else { + insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn); + conn.commit(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("xid {} branch {}, undo_log added with {}", xid, branchId, + State.GlobalFinished.name()); + } + } + + return; + } catch (SQLIntegrityConstraintViolationException e) { + // Possible undo_log has been inserted into the database by other processes, retrying rollback undo_log + if (LOGGER.isInfoEnabled()) { + LOGGER.info("xid {} branch {}, undo_log inserted, retry rollback", xid, branchId); + } + } catch (Throwable e) { + if (conn != null) { + try { + conn.rollback(); + } catch (SQLException rollbackEx) { + LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx); + } + } + throw new BranchTransactionException(BranchRollbackFailed_Retriable, String + .format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid, + branchId, e.getMessage()), e); + + } finally { + try { + if (rs != null) { + rs.close(); + } + if (selectPST != null) { + selectPST.close(); + } + if (conn != null) { + if (originalAutoCommit) { + conn.setAutoCommit(true); + } + conn.close(); + } + } catch (SQLException closeEx) { + LOGGER.warn("Failed to close JDBC resource while undo ... ", closeEx); + } + } + } + } + + /** + * insert uodo log when global finished + * + * @param xid the xid + * @param branchId the branchId + * @param undoLogParser the undoLogParse + * @param conn sql connection + * @throws SQLException SQLException + */ + protected abstract void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser undoLogParser, + Connection conn) throws SQLException; + + /** + * insert uodo log when normal + * + * @param xid the xid + * @param branchId the branchId + * @param rollbackCtx the rollbackContext + * @param undoLogContent the undoLogContent + * @param conn sql connection + * @throws SQLException SQLException + */ + protected abstract void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException; + + /** + * RollbackInfo to bytes + * + * @param rs + * @return + * @throws SQLException SQLException + */ + protected byte[] getRollbackInfo(ResultSet rs) throws SQLException { + byte[] rollbackInfo = rs.getBytes(ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO); + + String rollbackInfoContext = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT); + Map context = CollectionUtils.decodeMap(rollbackInfoContext); + CompressorType compressorType = CompressorType.getByName(context.getOrDefault(UndoLogConstants.COMPRESSOR_TYPE_KEY, + CompressorType.NONE.name())); + return CompressorFactory.getCompressor(compressorType.getCode()).decompress(rollbackInfo); + } + + /** + * if the undoLogContent is big enough to be compress + * @param undoLogContent undoLogContent + * @return boolean + */ + protected boolean needCompress(byte[] undoLogContent) { + return ROLLBACK_INFO_COMPRESS_ENABLE && undoLogContent.length > ROLLBACK_INFO_COMPRESS_THRESHOLD; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/BranchUndoLog.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/BranchUndoLog.java new file mode 100644 index 0000000..f8251af --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/BranchUndoLog.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.util.List; + +/** + * The type Branch undo log. + * + * @author sharajava + */ +public class BranchUndoLog implements java.io.Serializable { + + private static final long serialVersionUID = -101750721633603671L; + + private String xid; + + private long branchId; + + private List sqlUndoLogs; + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets sql undo logs. + * + * @return the sql undo logs + */ + public List getSqlUndoLogs() { + return sqlUndoLogs; + } + + /** + * Sets sql undo logs. + * + * @param sqlUndoLogs the sql undo logs + */ + public void setSqlUndoLogs(List sqlUndoLogs) { + this.sqlUndoLogs = sqlUndoLogs; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordChecker.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordChecker.java new file mode 100644 index 0000000..170aee1 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordChecker.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +/** + * The interface Keyword checker. + * + * @author Wu + */ +public interface KeywordChecker { + /** + * check whether given field name and table name use keywords + * + * @param fieldOrTableName the field or table name + * @return boolean + */ + boolean check(String fieldOrTableName); + + + /** + * check whether given field or table name use keywords. the method has database special logic. + * @param fieldOrTableName the field or table name + * @return true: need to escape. false: no need to escape. + */ + boolean checkEscape(String fieldOrTableName); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordCheckerFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordCheckerFactory.java new file mode 100644 index 0000000..a944204 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/KeywordCheckerFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The type Keyword checker factory. + * + * @author Wu + */ +public class KeywordCheckerFactory { + + private static final Map KEYWORD_CHECKER_MAP = new ConcurrentHashMap<>(); + + /** + * get keyword checker + * + * @param dbType the db type + * @return keyword checker + */ + public static KeywordChecker getKeywordChecker(String dbType) { + return CollectionUtils.computeIfAbsent(KEYWORD_CHECKER_MAP, dbType, + key -> EnhancedServiceLoader.load(KeywordChecker.class, dbType)); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/SQLUndoLog.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/SQLUndoLog.java new file mode 100644 index 0000000..69915c2 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/SQLUndoLog.java @@ -0,0 +1,125 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + + +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLType; + +/** + * The type Sql undo log. + * + * @author sharajava + */ +public class SQLUndoLog implements java.io.Serializable { + + private static final long serialVersionUID = -4160065043902060730L; + + private SQLType sqlType; + + private String tableName; + + private TableRecords beforeImage; + + private TableRecords afterImage; + + /** + * Sets table meta. + * + * @param tableMeta the table meta + */ + public void setTableMeta(TableMeta tableMeta) { + if (beforeImage != null) { + beforeImage.setTableMeta(tableMeta); + } + if (afterImage != null) { + afterImage.setTableMeta(tableMeta); + } + } + + /** + * Gets sql type. + * + * @return the sql type + */ + public SQLType getSqlType() { + return sqlType; + } + + /** + * Sets sql type. + * + * @param sqlType the sql type + */ + public void setSqlType(SQLType sqlType) { + this.sqlType = sqlType; + } + + /** + * Gets table name. + * + * @return the table name + */ + public String getTableName() { + return tableName; + } + + /** + * Sets table name. + * + * @param tableName the table name + */ + public void setTableName(String tableName) { + this.tableName = tableName; + } + + /** + * Gets before image. + * + * @return the before image + */ + public TableRecords getBeforeImage() { + return beforeImage; + } + + /** + * Sets before image. + * + * @param beforeImage the before image + */ + public void setBeforeImage(TableRecords beforeImage) { + this.beforeImage = beforeImage; + } + + /** + * Gets after image. + * + * @return the after image + */ + public TableRecords getAfterImage() { + return afterImage; + } + + /** + * Sets after image. + * + * @param afterImage the after image + */ + public void setAfterImage(TableRecords afterImage) { + this.afterImage = afterImage; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorFactory.java new file mode 100644 index 0000000..08c2f4b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.common.exception.ShouldNeverHappenException; + +/** + * The type Undo executor factory. + * + * @author sharajava + */ +public class UndoExecutorFactory { + + /** + * Gets undo executor. + * + * @param dbType the db type + * @param sqlUndoLog the sql undo log + * @return the undo executor + */ + public static AbstractUndoExecutor getUndoExecutor(String dbType, SQLUndoLog sqlUndoLog) { + AbstractUndoExecutor result = null; + UndoExecutorHolder holder = UndoExecutorHolderFactory.getUndoExecutorHolder(dbType.toLowerCase()); + switch (sqlUndoLog.getSqlType()) { + case INSERT: + result = holder.getInsertExecutor(sqlUndoLog); + break; + case UPDATE: + result = holder.getUpdateExecutor(sqlUndoLog); + break; + case DELETE: + result = holder.getDeleteExecutor(sqlUndoLog); + break; + default: + throw new ShouldNeverHappenException(); + } + return result; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolder.java new file mode 100644 index 0000000..aa85564 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolder.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +/** + * The Type UndoExecutorHolder + * + * @author: Zhibei Hao + */ +public interface UndoExecutorHolder { + + /** + * get the specific Insert UndoExecutor by sqlUndoLog + * + * @param sqlUndoLog the sqlUndoLog + * @return the specific UndoExecutor + */ + AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog); + + /** + * get the specific Update UndoExecutor by sqlUndoLog + * + * @param sqlUndoLog the sqlUndoLog + * @return the specific UndoExecutor + */ + AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog); + + /** + * get the specific Delete UndoExecutor by sqlUndoLog + * + * @param sqlUndoLog the sqlUndoLog + * @return the specific UndoExecutor + */ + AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolderFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolderFactory.java new file mode 100644 index 0000000..3c77721 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoExecutorHolderFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +/** + * The Type UndoExecutorHolderFactory + * + * @author: Zhibei Hao + */ +public class UndoExecutorHolderFactory { + + private static final Map UNDO_EXECUTOR_HOLDER_MAP = new ConcurrentHashMap<>(); + + /** + * Get UndoExecutorHolder by db type + * + * @param dbType the db type + * @return the UndoExecutorGroup + */ + public static UndoExecutorHolder getUndoExecutorHolder(String dbType) { + return CollectionUtils.computeIfAbsent(UNDO_EXECUTOR_HOLDER_MAP, dbType, + key -> EnhancedServiceLoader.load(UndoExecutorHolder.class, dbType)); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogConstants.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogConstants.java new file mode 100644 index 0000000..6e30c7f --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogConstants.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; + +import static io.seata.common.DefaultValues.DEFAULT_TRANSACTION_UNDO_LOG_SERIALIZATION; + +/** + * @author Geng Zhang + */ +public interface UndoLogConstants { + + String SERIALIZER_KEY = "serializer"; + + String DEFAULT_SERIALIZER = ConfigurationFactory.getInstance() + .getConfig(ConfigurationKeys.TRANSACTION_UNDO_LOG_SERIALIZATION, DEFAULT_TRANSACTION_UNDO_LOG_SERIALIZATION); + + String COMPRESSOR_TYPE_KEY = "compressorType"; +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManager.java new file mode 100644 index 0000000..e124951 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManager.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Date; +import java.util.Set; + +import io.seata.core.exception.TransactionException; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; + +/** + * The type Undo log manager. + * + * @author sharajava + * @author Geng Zhang + */ +public interface UndoLogManager { + + /** + * Flush undo logs. + * @param cp the cp + * @throws SQLException the sql exception + */ + void flushUndoLogs(ConnectionProxy cp) throws SQLException; + + /** + * Undo. + * + * @param dataSourceProxy the data source proxy + * @param xid the xid + * @param branchId the branch id + * @throws TransactionException the transaction exception + */ + void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException; + + /** + * Delete undo log. + * + * @param xid the xid + * @param branchId the branch id + * @param conn the conn + * @throws SQLException the sql exception + */ + void deleteUndoLog(String xid, long branchId, Connection conn) throws SQLException; + + /** + * batch Delete undo log. + * + * @param xids the xid set collections + * @param branchIds the branch id set collections + * @param conn the connection + * @throws SQLException the sql exception + */ + void batchDeleteUndoLog(Set xids, Set branchIds, Connection conn) throws SQLException; + + /** + * delete undolog by created + * @param logCreated the created time + * @param limitRows the limit rows + * @param conn the connection + * @return the update rows + * @throws SQLException the sql exception + */ + int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException; + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManagerFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManagerFactory.java new file mode 100644 index 0000000..c41c157 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogManagerFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +/** + * @author jsbxyyx + */ +public class UndoLogManagerFactory { + + private static final Map UNDO_LOG_MANAGER_MAP = new ConcurrentHashMap<>(); + + /** + * get undo log manager. + * + * @param dbType the db type + * @return undo log manager. + */ + public static UndoLogManager getUndoLogManager(String dbType) { + return CollectionUtils.computeIfAbsent(UNDO_LOG_MANAGER_MAP, dbType, + key -> EnhancedServiceLoader.load(UndoLogManager.class, dbType)); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParser.java new file mode 100644 index 0000000..6728b30 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParser.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +/** + * The interface Undo log parser. + * + * @author sharajava + * @author Geng Zhang + */ +public interface UndoLogParser { + + /** + * Get the name of parser; + * + * @return the name of parser + */ + String getName(); + + /** + * Get default context of this parser + * + * @return the default content if undo log is empty + */ + byte[] getDefaultContent(); + + /** + * Encode branch undo log to byte array. + * + * @param branchUndoLog the branch undo log + * @return the byte array + */ + byte[] encode(BranchUndoLog branchUndoLog); + + /** + * Decode byte array to branch undo log. + * + * @param bytes the byte array + * @return the branch undo log + */ + BranchUndoLog decode(byte[] bytes); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParserFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParserFactory.java new file mode 100644 index 0000000..1dbbf6c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/UndoLogParserFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * The type Undo log parser factory. + * + * @author sharajava + * @author Geng Zhang + */ +public class UndoLogParserFactory { + + private UndoLogParserFactory() { + + } + + /** + * {serializerName:UndoLogParser} + */ + private static final ConcurrentMap INSTANCES = new ConcurrentHashMap<>(); + + private static class SingletonHolder { + private static final UndoLogParser INSTANCE = getInstance(UndoLogConstants.DEFAULT_SERIALIZER); + } + + /** + * Gets default UndoLogParser instance. + * + * @return the instance + */ + public static UndoLogParser getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * Gets UndoLogParser by name + * + * @param name parser name + * @return the UndoLogParser + */ + public static UndoLogParser getInstance(String name) { + return CollectionUtils.computeIfAbsent(INSTANCES, name, + key -> EnhancedServiceLoader.load(UndoLogParser.class, name)); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java new file mode 100644 index 0000000..56bc951 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutor.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type My sql undo delete executor. + * + * @author sharajava + */ +public class MySQLUndoDeleteExecutor extends AbstractUndoExecutor { + + /** + * Instantiates a new My sql undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoDeleteExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?) + */ + private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)"; + + /** + * Undo delete. + * + * Notice: PK is at last one. + * @see AbstractUndoExecutor#undoPrepare + * + * @return sql + */ + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + List fields = new ArrayList<>(row.nonPrimaryKeys()); + fields.addAll(getOrderedPkList(beforeImage,row,JdbcConstants.MYSQL)); + + // delete sql undo log before image all field come from table meta, need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String insertColumns = fields.stream() + .map(field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.MYSQL)) + .collect(Collectors.joining(", ")); + String insertValues = fields.stream().map(field -> "?") + .collect(Collectors.joining(", ")); + + return String.format(INSERT_SQL_TEMPLATE, sqlUndoLog.getTableName(), insertColumns, insertValues); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoExecutorHolder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoExecutorHolder.java new file mode 100644 index 0000000..ac26d15 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoExecutorHolder.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoExecutorHolder; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The Type MySQLUndoExecutorHolder + * + * @author: Zhibei Hao + */ +@LoadLevel(name = JdbcConstants.MYSQL) +public class MySQLUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new MySQLUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new MySQLUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new MySQLUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java new file mode 100644 index 0000000..ae2a45c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutor.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type My sql undo insert executor. + * + * @author sharajava + */ +public class MySQLUndoInsertExecutor extends AbstractUndoExecutor { + + /** + * DELETE FROM a WHERE pk = ? + */ + private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s "; + + /** + * Undo Inset. + * + * @return sql + */ + @Override + protected String buildUndoSQL() { + TableRecords afterImage = sqlUndoLog.getAfterImage(); + List afterImageRows = afterImage.getRows(); + if (CollectionUtils.isEmpty(afterImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + return generateDeleteSql(afterImageRows,afterImage); + } + + @Override + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) + throws SQLException { + int undoIndex = 0; + for (Field pkField:pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + } + + private String generateDeleteSql(List rows, TableRecords afterImage) { + List pkNameList = getOrderedPkList(afterImage, rows.get(0), JdbcConstants.MYSQL).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.MYSQL); + return String.format(DELETE_SQL_TEMPLATE, sqlUndoLog.getTableName(), whereSql); + } + + /** + * Instantiates a new My sql undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoInsertExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManager.java new file mode 100644 index 0000000..91c754b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManager.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.CompressorType; +import io.seata.core.constants.ClientTableColumnsName; +import io.seata.rm.datasource.undo.AbstractUndoLogManager; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jsbxyyx + */ +@LoadLevel(name = JdbcConstants.MYSQL) +public class MySQLUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(MySQLUndoLogManager.class); + + /** + * branch_id, xid, context, rollback_info, log_status, log_created, log_modified + */ + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + ClientTableColumnsName.UNDO_LOG_XID + ", " + + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + " VALUES (?, ?, ?, ?, ?, now(6), now(6))"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= ? LIMIT ?"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { + deletePST.setDate(1, new java.sql.Date(logCreated.getTime())); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId, rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), State.GlobalFinished, conn); + } + + private void insertUndoLog(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { + pst.setLong(1, branchId); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java new file mode 100644 index 0000000..063f831 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutor.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type My sql undo update executor. + * + * @author sharajava + */ +public class MySQLUndoUpdateExecutor extends AbstractUndoExecutor { + + /** + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 in (?) pk2 in (?) + */ + private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; + + /** + * Undo Update. + * + * @return sql + */ + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO + } + Row row = beforeImageRows.get(0); + + List nonPkFields = row.nonPrimaryKeys(); + // update sql undo log before image all field come from table meta. need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String updateColumns = nonPkFields.stream().map( + field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.MYSQL) + " = ?").collect( + Collectors.joining(", ")); + + List pkNameList = getOrderedPkList(beforeImage, row, JdbcConstants.MYSQL).stream().map(e -> e.getName()) + .collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.MYSQL); + + return String.format(UPDATE_SQL_TEMPLATE, sqlUndoLog.getTableName(), updateColumns, whereSql); + } + + /** + * Instantiates a new My sql undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoUpdateExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java new file mode 100644 index 0000000..7589f46 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordChecker.java @@ -0,0 +1,1120 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql.keyword; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type MySQL keyword checker. + * + * @author xingfudeshi@gmail.com + */ +@LoadLevel(name = JdbcConstants.MYSQL) +public class MySQLKeywordChecker implements KeywordChecker { + + private Set keywordSet = Arrays.stream(MySQLKeyword.values()).map(MySQLKeyword::name).collect(Collectors.toSet()); + + /** + * MySQL keyword + */ + private enum MySQLKeyword { + /** + * ACCESSIBLE is mysql keyword. + */ + ACCESSIBLE("ACCESSIBLE"), + /** + * ADD is mysql keyword. + */ + ADD("ADD"), + /** + * ALL is mysql keyword. + */ + ALL("ALL"), + /** + * ALTER is mysql keyword. + */ + ALTER("ALTER"), + /** + * ANALYZE is mysql keyword. + */ + ANALYZE("ANALYZE"), + /** + * AND is mysql keyword. + */ + AND("AND"), + /** + * ARRAY is mysql keyword. + */ + ARRAY("ARRAY"), + /** + * AS is mysql keyword. + */ + AS("AS"), + /** + * ASC is mysql keyword. + */ + ASC("ASC"), + /** + * ASENSITIVE is mysql keyword. + */ + ASENSITIVE("ASENSITIVE"), + /** + * BEFORE is mysql keyword. + */ + BEFORE("BEFORE"), + /** + * BETWEEN is mysql keyword. + */ + BETWEEN("BETWEEN"), + /** + * BIGINT is mysql keyword. + */ + BIGINT("BIGINT"), + /** + * BINARY is mysql keyword. + */ + BINARY("BINARY"), + /** + * BLOB is mysql keyword. + */ + BLOB("BLOB"), + /** + * BOTH is mysql keyword. + */ + BOTH("BOTH"), + /** + * BY is mysql keyword. + */ + BY("BY"), + /** + * CALL is mysql keyword. + */ + CALL("CALL"), + /** + * CASCADE is mysql keyword. + */ + CASCADE("CASCADE"), + /** + * CASE is mysql keyword. + */ + CASE("CASE"), + /** + * CHANGE is mysql keyword. + */ + CHANGE("CHANGE"), + /** + * CHAR is mysql keyword. + */ + CHAR("CHAR"), + /** + * CHARACTER is mysql keyword. + */ + CHARACTER("CHARACTER"), + /** + * CHECK is mysql keyword. + */ + CHECK("CHECK"), + /** + * COLLATE is mysql keyword. + */ + COLLATE("COLLATE"), + /** + * COLUMN is mysql keyword. + */ + COLUMN("COLUMN"), + /** + * CONDITION is mysql keyword. + */ + CONDITION("CONDITION"), + /** + * CONSTRAINT is mysql keyword. + */ + CONSTRAINT("CONSTRAINT"), + /** + * CONTINUE is mysql keyword. + */ + CONTINUE("CONTINUE"), + /** + * CONVERT is mysql keyword. + */ + CONVERT("CONVERT"), + /** + * CREATE is mysql keyword. + */ + CREATE("CREATE"), + /** + * CROSS is mysql keyword. + */ + CROSS("CROSS"), + /** + * CUBE is mysql keyword. + */ + CUBE("CUBE"), + /** + * CUME_DIST is mysql keyword. + */ + CUME_DIST("CUME_DIST"), + /** + * CURRENT_DATE is mysql keyword. + */ + CURRENT_DATE("CURRENT_DATE"), + /** + * CURRENT_TIME is mysql keyword. + */ + CURRENT_TIME("CURRENT_TIME"), + /** + * CURRENT_TIMESTAMP is mysql keyword. + */ + CURRENT_TIMESTAMP("CURRENT_TIMESTAMP"), + /** + * CURRENT_USER is mysql keyword. + */ + CURRENT_USER("CURRENT_USER"), + /** + * CURSOR is mysql keyword. + */ + CURSOR("CURSOR"), + /** + * DATABASE is mysql keyword. + */ + DATABASE("DATABASE"), + /** + * DATABASES is mysql keyword. + */ + DATABASES("DATABASES"), + /** + * DAY_HOUR is mysql keyword. + */ + DAY_HOUR("DAY_HOUR"), + /** + * DAY_MICROSECOND is mysql keyword. + */ + DAY_MICROSECOND("DAY_MICROSECOND"), + /** + * DAY_MINUTE is mysql keyword. + */ + DAY_MINUTE("DAY_MINUTE"), + /** + * DAY_SECOND is mysql keyword. + */ + DAY_SECOND("DAY_SECOND"), + /** + * DEC is mysql keyword. + */ + DEC("DEC"), + /** + * DECIMAL is mysql keyword. + */ + DECIMAL("DECIMAL"), + /** + * DECLARE is mysql keyword. + */ + DECLARE("DECLARE"), + /** + * DEFAULT is mysql keyword. + */ + DEFAULT("DEFAULT"), + /** + * DELAYED is mysql keyword. + */ + DELAYED("DELAYED"), + /** + * DELETE is mysql keyword. + */ + DELETE("DELETE"), + /** + * DENSE_RANK is mysql keyword. + */ + DENSE_RANK("DENSE_RANK"), + /** + * DESC is mysql keyword. + */ + DESC("DESC"), + /** + * DESCRIBE is mysql keyword. + */ + DESCRIBE("DESCRIBE"), + /** + * DETERMINISTIC is mysql keyword. + */ + DETERMINISTIC("DETERMINISTIC"), + /** + * DISTINCT is mysql keyword. + */ + DISTINCT("DISTINCT"), + /** + * DISTINCTROW is mysql keyword. + */ + DISTINCTROW("DISTINCTROW"), + /** + * DIV is mysql keyword. + */ + DIV("DIV"), + /** + * DOUBLE is mysql keyword. + */ + DOUBLE("DOUBLE"), + /** + * DROP is mysql keyword. + */ + DROP("DROP"), + /** + * DUAL is mysql keyword. + */ + DUAL("DUAL"), + /** + * EACH is mysql keyword. + */ + EACH("EACH"), + /** + * ELSE is mysql keyword. + */ + ELSE("ELSE"), + /** + * ELSEIF is mysql keyword. + */ + ELSEIF("ELSEIF"), + /** + * EMPTY is mysql keyword. + */ + EMPTY("EMPTY"), + /** + * ENCLOSED is mysql keyword. + */ + ENCLOSED("ENCLOSED"), + /** + * ESCAPED is mysql keyword. + */ + ESCAPED("ESCAPED"), + /** + * EXCEPT is mysql keyword. + */ + EXCEPT("EXCEPT"), + /** + * EXISTS is mysql keyword. + */ + EXISTS("EXISTS"), + /** + * EXIT is mysql keyword. + */ + EXIT("EXIT"), + /** + * EXPLAIN is mysql keyword. + */ + EXPLAIN("EXPLAIN"), + /** + * FALSE is mysql keyword. + */ + FALSE("FALSE"), + /** + * FETCH is mysql keyword. + */ + FETCH("FETCH"), + /** + * FIRST_VALUE is mysql keyword. + */ + FIRST_VALUE("FIRST_VALUE"), + /** + * FLOAT is mysql keyword. + */ + FLOAT("FLOAT"), + /** + * FLOAT4 is mysql keyword. + */ + FLOAT4("FLOAT4"), + /** + * FLOAT8 is mysql keyword. + */ + FLOAT8("FLOAT8"), + /** + * FOR is mysql keyword. + */ + FOR("FOR"), + /** + * FORCE is mysql keyword. + */ + FORCE("FORCE"), + /** + * FOREIGN is mysql keyword. + */ + FOREIGN("FOREIGN"), + /** + * FROM is mysql keyword. + */ + FROM("FROM"), + /** + * FULLTEXT is mysql keyword. + */ + FULLTEXT("FULLTEXT"), + /** + * FUNCTION is mysql keyword. + */ + FUNCTION("FUNCTION"), + /** + * GENERATED is mysql keyword. + */ + GENERATED("GENERATED"), + /** + * GET is mysql keyword. + */ + GET("GET"), + /** + * GRANT is mysql keyword. + */ + GRANT("GRANT"), + /** + * GROUP is mysql keyword. + */ + GROUP("GROUP"), + /** + * GROUPING is mysql keyword. + */ + GROUPING("GROUPING"), + /** + * GROUPS is mysql keyword. + */ + GROUPS("GROUPS"), + /** + * HAVING is mysql keyword. + */ + HAVING("HAVING"), + /** + * HIGH_PRIORITY is mysql keyword. + */ + HIGH_PRIORITY("HIGH_PRIORITY"), + /** + * HOUR_MICROSECOND is mysql keyword. + */ + HOUR_MICROSECOND("HOUR_MICROSECOND"), + /** + * HOUR_MINUTE is mysql keyword. + */ + HOUR_MINUTE("HOUR_MINUTE"), + /** + * HOUR_SECOND is mysql keyword. + */ + HOUR_SECOND("HOUR_SECOND"), + /** + * IF is mysql keyword. + */ + IF("IF"), + /** + * IGNORE is mysql keyword. + */ + IGNORE("IGNORE"), + /** + * IN is mysql keyword. + */ + IN("IN"), + /** + * INDEX is mysql keyword. + */ + INDEX("INDEX"), + /** + * INFILE is mysql keyword. + */ + INFILE("INFILE"), + /** + * INNER is mysql keyword. + */ + INNER("INNER"), + /** + * INOUT is mysql keyword. + */ + INOUT("INOUT"), + /** + * INSENSITIVE is mysql keyword. + */ + INSENSITIVE("INSENSITIVE"), + /** + * INSERT is mysql keyword. + */ + INSERT("INSERT"), + /** + * INT is mysql keyword. + */ + INT("INT"), + /** + * INT1 is mysql keyword. + */ + INT1("INT1"), + /** + * INT2 is mysql keyword. + */ + INT2("INT2"), + /** + * INT3 is mysql keyword. + */ + INT3("INT3"), + /** + * INT4 is mysql keyword. + */ + INT4("INT4"), + /** + * INT8 is mysql keyword. + */ + INT8("INT8"), + /** + * INTEGER is mysql keyword. + */ + INTEGER("INTEGER"), + /** + * INTERVAL is mysql keyword. + */ + INTERVAL("INTERVAL"), + /** + * INTO is mysql keyword. + */ + INTO("INTO"), + /** + * IO_AFTER_GTIDS is mysql keyword. + */ + IO_AFTER_GTIDS("IO_AFTER_GTIDS"), + /** + * IO_BEFORE_GTIDS is mysql keyword. + */ + IO_BEFORE_GTIDS("IO_BEFORE_GTIDS"), + /** + * IS is mysql keyword. + */ + IS("IS"), + /** + * ITERATE is mysql keyword. + */ + ITERATE("ITERATE"), + /** + * JOIN is mysql keyword. + */ + JOIN("JOIN"), + /** + * JSON_TABLE is mysql keyword. + */ + JSON_TABLE("JSON_TABLE"), + /** + * KEY is mysql keyword. + */ + KEY("KEY"), + /** + * KEYS is mysql keyword. + */ + KEYS("KEYS"), + /** + * KILL is mysql keyword. + */ + KILL("KILL"), + /** + * LAG is mysql keyword. + */ + LAG("LAG"), + /** + * LAST_VALUE is mysql keyword. + */ + LAST_VALUE("LAST_VALUE"), + /** + * LATERAL is mysql keyword. + */ + LATERAL("LATERAL"), + /** + * LEAD is mysql keyword. + */ + LEAD("LEAD"), + /** + * LEADING is mysql keyword. + */ + LEADING("LEADING"), + /** + * LEAVE is mysql keyword. + */ + LEAVE("LEAVE"), + /** + * LEFT is mysql keyword. + */ + LEFT("LEFT"), + /** + * LIKE is mysql keyword. + */ + LIKE("LIKE"), + /** + * LIMIT is mysql keyword. + */ + LIMIT("LIMIT"), + /** + * LINEAR is mysql keyword. + */ + LINEAR("LINEAR"), + /** + * LINES is mysql keyword. + */ + LINES("LINES"), + /** + * LOAD is mysql keyword. + */ + LOAD("LOAD"), + /** + * LOCALTIME is mysql keyword. + */ + LOCALTIME("LOCALTIME"), + /** + * LOCALTIMESTAMP is mysql keyword. + */ + LOCALTIMESTAMP("LOCALTIMESTAMP"), + /** + * LOCK is mysql keyword. + */ + LOCK("LOCK"), + /** + * LONG is mysql keyword. + */ + LONG("LONG"), + /** + * LONGBLOB is mysql keyword. + */ + LONGBLOB("LONGBLOB"), + /** + * LONGTEXT is mysql keyword. + */ + LONGTEXT("LONGTEXT"), + /** + * LOOP is mysql keyword. + */ + LOOP("LOOP"), + /** + * LOW_PRIORITY is mysql keyword. + */ + LOW_PRIORITY("LOW_PRIORITY"), + /** + * MASTER_BIND is mysql keyword. + */ + MASTER_BIND("MASTER_BIND"), + /** + * MASTER_SSL_VERIFY_SERVER_CERT is mysql keyword. + */ + MASTER_SSL_VERIFY_SERVER_CERT("MASTER_SSL_VERIFY_SERVER_CERT"), + /** + * MATCH is mysql keyword. + */ + MATCH("MATCH"), + /** + * MAXVALUE is mysql keyword. + */ + MAXVALUE("MAXVALUE"), + /** + * MEDIUMBLOB is mysql keyword. + */ + MEDIUMBLOB("MEDIUMBLOB"), + /** + * MEDIUMINT is mysql keyword. + */ + MEDIUMINT("MEDIUMINT"), + /** + * MEDIUMTEXT is mysql keyword. + */ + MEDIUMTEXT("MEDIUMTEXT"), + /** + * MEMBER is mysql keyword. + */ + MEMBER("MEMBER"), + /** + * MIDDLEINT is mysql keyword. + */ + MIDDLEINT("MIDDLEINT"), + /** + * MINUTE_MICROSECOND is mysql keyword. + */ + MINUTE_MICROSECOND("MINUTE_MICROSECOND"), + /** + * MINUTE_SECOND is mysql keyword. + */ + MINUTE_SECOND("MINUTE_SECOND"), + /** + * MOD is mysql keyword. + */ + MOD("MOD"), + /** + * MODIFIES is mysql keyword. + */ + MODIFIES("MODIFIES"), + /** + * NATURAL is mysql keyword. + */ + NATURAL("NATURAL"), + /** + * NOT is mysql keyword. + */ + NOT("NOT"), + /** + * NO_WRITE_TO_BINLOG is mysql keyword. + */ + NO_WRITE_TO_BINLOG("NO_WRITE_TO_BINLOG"), + /** + * NTH_VALUE is mysql keyword. + */ + NTH_VALUE("NTH_VALUE"), + /** + * NTILE is mysql keyword. + */ + NTILE("NTILE"), + /** + * NULL is mysql keyword. + */ + NULL("NULL"), + /** + * NUMERIC is mysql keyword. + */ + NUMERIC("NUMERIC"), + /** + * OF is mysql keyword. + */ + OF("OF"), + /** + * ON is mysql keyword. + */ + ON("ON"), + /** + * OPTIMIZE is mysql keyword. + */ + OPTIMIZE("OPTIMIZE"), + /** + * OPTIMIZER_COSTS is mysql keyword. + */ + OPTIMIZER_COSTS("OPTIMIZER_COSTS"), + /** + * OPTION is mysql keyword. + */ + OPTION("OPTION"), + /** + * OPTIONALLY is mysql keyword. + */ + OPTIONALLY("OPTIONALLY"), + /** + * OR is mysql keyword. + */ + OR("OR"), + /** + * ORDER is mysql keyword. + */ + ORDER("ORDER"), + /** + * OUT is mysql keyword. + */ + OUT("OUT"), + /** + * OUTER is mysql keyword. + */ + OUTER("OUTER"), + /** + * OUTFILE is mysql keyword. + */ + OUTFILE("OUTFILE"), + /** + * OVER is mysql keyword. + */ + OVER("OVER"), + /** + * PARTITION is mysql keyword. + */ + PARTITION("PARTITION"), + /** + * PERCENT_RANK is mysql keyword. + */ + PERCENT_RANK("PERCENT_RANK"), + /** + * PRECISION is mysql keyword. + */ + PRECISION("PRECISION"), + /** + * PRIMARY is mysql keyword. + */ + PRIMARY("PRIMARY"), + /** + * PROCEDURE is mysql keyword. + */ + PROCEDURE("PROCEDURE"), + /** + * PURGE is mysql keyword. + */ + PURGE("PURGE"), + /** + * RANGE is mysql keyword. + */ + RANGE("RANGE"), + /** + * RANK is mysql keyword. + */ + RANK("RANK"), + /** + * READ is mysql keyword. + */ + READ("READ"), + /** + * READS is mysql keyword. + */ + READS("READS"), + /** + * READ_WRITE is mysql keyword. + */ + READ_WRITE("READ_WRITE"), + /** + * REAL is mysql keyword. + */ + REAL("REAL"), + /** + * RECURSIVE is mysql keyword. + */ + RECURSIVE("RECURSIVE"), + /** + * REFERENCES is mysql keyword. + */ + REFERENCES("REFERENCES"), + /** + * REGEXP is mysql keyword. + */ + REGEXP("REGEXP"), + /** + * RELEASE is mysql keyword. + */ + RELEASE("RELEASE"), + /** + * RENAME is mysql keyword. + */ + RENAME("RENAME"), + /** + * REPEAT is mysql keyword. + */ + REPEAT("REPEAT"), + /** + * REPLACE is mysql keyword. + */ + REPLACE("REPLACE"), + /** + * REQUIRE is mysql keyword. + */ + REQUIRE("REQUIRE"), + /** + * RESIGNAL is mysql keyword. + */ + RESIGNAL("RESIGNAL"), + /** + * RESTRICT is mysql keyword. + */ + RESTRICT("RESTRICT"), + /** + * RETURN is mysql keyword. + */ + RETURN("RETURN"), + /** + * REVOKE is mysql keyword. + */ + REVOKE("REVOKE"), + /** + * RIGHT is mysql keyword. + */ + RIGHT("RIGHT"), + /** + * RLIKE is mysql keyword. + */ + RLIKE("RLIKE"), + /** + * ROW is mysql keyword. + */ + ROW("ROW"), + /** + * ROWS is mysql keyword. + */ + ROWS("ROWS"), + /** + * ROW_NUMBER is mysql keyword. + */ + ROW_NUMBER("ROW_NUMBER"), + /** + * SCHEMA is mysql keyword. + */ + SCHEMA("SCHEMA"), + /** + * SCHEMAS is mysql keyword. + */ + SCHEMAS("SCHEMAS"), + /** + * SECOND_MICROSECOND is mysql keyword. + */ + SECOND_MICROSECOND("SECOND_MICROSECOND"), + /** + * SELECT is mysql keyword. + */ + SELECT("SELECT"), + /** + * SENSITIVE is mysql keyword. + */ + SENSITIVE("SENSITIVE"), + /** + * SEPARATOR is mysql keyword. + */ + SEPARATOR("SEPARATOR"), + /** + * SET is mysql keyword. + */ + SET("SET"), + /** + * SHOW is mysql keyword. + */ + SHOW("SHOW"), + /** + * SIGNAL is mysql keyword. + */ + SIGNAL("SIGNAL"), + /** + * SMALLINT is mysql keyword. + */ + SMALLINT("SMALLINT"), + /** + * SPATIAL is mysql keyword. + */ + SPATIAL("SPATIAL"), + /** + * SPECIFIC is mysql keyword. + */ + SPECIFIC("SPECIFIC"), + /** + * SQL is mysql keyword. + */ + SQL("SQL"), + /** + * SQLEXCEPTION is mysql keyword. + */ + SQLEXCEPTION("SQLEXCEPTION"), + /** + * SQLSTATE is mysql keyword. + */ + SQLSTATE("SQLSTATE"), + /** + * SQLWARNING is mysql keyword. + */ + SQLWARNING("SQLWARNING"), + /** + * SQL_BIG_RESULT is mysql keyword. + */ + SQL_BIG_RESULT("SQL_BIG_RESULT"), + /** + * SQL_CALC_FOUND_ROWS is mysql keyword. + */ + SQL_CALC_FOUND_ROWS("SQL_CALC_FOUND_ROWS"), + /** + * SQL_SMALL_RESULT is mysql keyword. + */ + SQL_SMALL_RESULT("SQL_SMALL_RESULT"), + /** + * SSL is mysql keyword. + */ + SSL("SSL"), + /** + * STARTING is mysql keyword. + */ + STARTING("STARTING"), + /** + * STORED is mysql keyword. + */ + STORED("STORED"), + /** + * STRAIGHT_JOIN is mysql keyword. + */ + STRAIGHT_JOIN("STRAIGHT_JOIN"), + /** + * SYSTEM is mysql keyword. + */ + SYSTEM("SYSTEM"), + /** + * TABLE is mysql keyword. + */ + TABLE("TABLE"), + /** + * TERMINATED is mysql keyword. + */ + TERMINATED("TERMINATED"), + /** + * THEN is mysql keyword. + */ + THEN("THEN"), + /** + * TINYBLOB is mysql keyword. + */ + TINYBLOB("TINYBLOB"), + /** + * TINYINT is mysql keyword. + */ + TINYINT("TINYINT"), + /** + * TINYTEXT is mysql keyword. + */ + TINYTEXT("TINYTEXT"), + /** + * TO is mysql keyword. + */ + TO("TO"), + /** + * TRAILING is mysql keyword. + */ + TRAILING("TRAILING"), + /** + * TRIGGER is mysql keyword. + */ + TRIGGER("TRIGGER"), + /** + * TRUE is mysql keyword. + */ + TRUE("TRUE"), + /** + * UNDO is mysql keyword. + */ + UNDO("UNDO"), + /** + * UNION is mysql keyword. + */ + UNION("UNION"), + /** + * UNIQUE is mysql keyword. + */ + UNIQUE("UNIQUE"), + /** + * UNLOCK is mysql keyword. + */ + UNLOCK("UNLOCK"), + /** + * UNSIGNED is mysql keyword. + */ + UNSIGNED("UNSIGNED"), + /** + * UPDATE is mysql keyword. + */ + UPDATE("UPDATE"), + /** + * USAGE is mysql keyword. + */ + USAGE("USAGE"), + /** + * USE is mysql keyword. + */ + USE("USE"), + /** + * USING is mysql keyword. + */ + USING("USING"), + /** + * UTC_DATE is mysql keyword. + */ + UTC_DATE("UTC_DATE"), + /** + * UTC_TIME is mysql keyword. + */ + UTC_TIME("UTC_TIME"), + /** + * UTC_TIMESTAMP is mysql keyword. + */ + UTC_TIMESTAMP("UTC_TIMESTAMP"), + /** + * VALUES is mysql keyword. + */ + VALUES("VALUES"), + /** + * VARBINARY is mysql keyword. + */ + VARBINARY("VARBINARY"), + /** + * VARCHAR is mysql keyword. + */ + VARCHAR("VARCHAR"), + /** + * VARCHARACTER is mysql keyword. + */ + VARCHARACTER("VARCHARACTER"), + /** + * VARYING is mysql keyword. + */ + VARYING("VARYING"), + /** + * VIRTUAL is mysql keyword. + */ + VIRTUAL("VIRTUAL"), + /** + * WHEN is mysql keyword. + */ + WHEN("WHEN"), + /** + * WHERE is mysql keyword. + */ + WHERE("WHERE"), + /** + * WHILE is mysql keyword. + */ + WHILE("WHILE"), + /** + * WINDOW is mysql keyword. + */ + WINDOW("WINDOW"), + /** + * WITH is mysql keyword. + */ + WITH("WITH"), + /** + * WRITE is mysql keyword. + */ + WRITE("WRITE"), + /** + * XOR is mysql keyword. + */ + XOR("XOR"), + /** + * YEAR_MONTH is mysql keyword. + */ + YEAR_MONTH("YEAR_MONTH"), + /** + * ZEROFILL is mysql keyword. + */ + ZEROFILL("ZEROFILL"); + /** + * The Name. + */ + public final String name; + + MySQLKeyword(String name) { + this.name = name; + } + } + + + @Override + public boolean check(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + @Override + public boolean checkEscape(String fieldOrTableName) { + return check(fieldOrTableName); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutor.java new file mode 100644 index 0000000..570d194 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutor.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type oracle undo delete executor. + * + * @author ccg + */ +public class OracleUndoDeleteExecutor extends AbstractUndoExecutor { + + /** + * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?) + */ + private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)"; + + /** + * Instantiates a new oracle undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public OracleUndoDeleteExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + List fields = new ArrayList<>(row.nonPrimaryKeys()); + fields.addAll(getOrderedPkList(beforeImage,row,JdbcConstants.ORACLE)); + + // delete sql undo log before image all field come from table meta, need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String insertColumns = fields.stream() + .map(field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.ORACLE)) + .collect(Collectors.joining(", ")); + String insertValues = fields.stream().map(field -> "?") + .collect(Collectors.joining(", ")); + + return String.format(INSERT_SQL_TEMPLATE, sqlUndoLog.getTableName(), insertColumns, insertValues); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoExecutorHolder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoExecutorHolder.java new file mode 100644 index 0000000..91350a4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoExecutorHolder.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoExecutorHolder; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The Type OracleUndoExecutorHolder + * + * @author: Zhibei Hao + */ +@LoadLevel(name = JdbcConstants.ORACLE) +public class OracleUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new OracleUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java new file mode 100644 index 0000000..6840c0a --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type oralce undo insert executor. + * + * @author ccg + */ +public class OracleUndoInsertExecutor extends AbstractUndoExecutor { + + /** + * DELETE FROM a WHERE pk = ? + */ + private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords afterImage = sqlUndoLog.getAfterImage(); + List afterImageRows = afterImage.getRows(); + if (CollectionUtils.isEmpty(afterImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + return generateDeleteSql(afterImageRows,afterImage); + } + + @Override + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) + throws SQLException { + int undoIndex = 0; + for (Field pkField:pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + } + + private String generateDeleteSql(List rows, TableRecords afterImage) { + List pkNameList = getOrderedPkList(afterImage, rows.get(0), JdbcConstants.ORACLE).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.ORACLE); + return String.format(DELETE_SQL_TEMPLATE, sqlUndoLog.getTableName(), whereSql); + } + + /** + * Instantiates a new My sql undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public OracleUndoInsertExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoLogManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoLogManager.java new file mode 100644 index 0000000..beb7bc4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoLogManager.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.CompressorType; +import io.seata.core.constants.ClientTableColumnsName; +import io.seata.rm.datasource.undo.AbstractUndoLogManager; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jsbxyyx + */ +@LoadLevel(name = JdbcConstants.ORACLE) +public class OracleUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(OracleUndoLogManager.class); + + + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_ID + "," + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + + ClientTableColumnsName.UNDO_LOG_XID + ", " + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + "VALUES (UNDO_LOG_SEQ.nextval, ?, ?, ?, ?, ?, sysdate, sysdate)"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= ? and ROWNUM <= ?"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { + deletePST.setDate(1, new java.sql.Date(logCreated.getTime())); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId,rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), + State.GlobalFinished, conn); + } + + + private void insertUndoLog(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { + pst.setLong(1, branchID); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutor.java new file mode 100644 index 0000000..220875c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutor.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type oracle undo update executor. + * + * @author ccg + */ +public class OracleUndoUpdateExecutor extends AbstractUndoExecutor { + + /** + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 = ? and pk2 = ? + */ + private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO + } + Row row = beforeImageRows.get(0); + + List nonPkFields = row.nonPrimaryKeys(); + // update sql undo log before image all field come from table meta. need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String updateColumns = nonPkFields.stream().map( + field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.ORACLE) + " = ?").collect( + Collectors.joining(", ")); + + List pkNameList = getOrderedPkList(beforeImage, row, JdbcConstants.ORACLE).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.ORACLE); + + return String.format(UPDATE_SQL_TEMPLATE, sqlUndoLog.getTableName(), updateColumns, whereSql); + } + + /** + * Instantiates a new My sql undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public OracleUndoUpdateExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordChecker.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordChecker.java new file mode 100644 index 0000000..6b82e72 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordChecker.java @@ -0,0 +1,526 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle.keyword; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type oracle sql keyword checker. + * + * @author ccg + */ +@LoadLevel(name = JdbcConstants.ORACLE) +public class OracleKeywordChecker implements KeywordChecker { + + private Set keywordSet = Arrays.stream(OracleKeyword.values()).map(OracleKeyword::name).collect(Collectors.toSet()); + + /** + * oracle keyword + */ + private enum OracleKeyword { + /** + * ACCESS is oracle keyword + */ + ACCESS("ACCESS"), + /** + * ADD is oracle keyword + */ + ADD("ADD"), + /** + * ALL is oracle keyword + */ + ALL("ALL"), + /** + * ALTER is oracle keyword + */ + ALTER("ALTER"), + /** + * AND is oracle keyword + */ + AND("AND"), + /** + * ANY is oracle keyword + */ + ANY("ANY"), + /** + * AS is oracle keyword + */ + AS("AS"), + /** + * ASC is oracle keyword + */ + ASC("ASC"), + /** + * AUDIT is oracle keyword + */ + AUDIT("AUDIT"), + /** + * BETWEEN is oracle keyword + */ + BETWEEN("BETWEEN"), + /** + * BY is oracle keyword + */ + BY("BY"), + /** + * CHAR is oracle keyword + */ + CHAR("CHAR"), + /** + * CHECK is oracle keyword + */ + CHECK("CHECK"), + /** + * CLUSTER is oracle keyword + */ + CLUSTER("CLUSTER"), + /** + * COLUMN is oracle keyword + */ + COLUMN("COLUMN"), + /** + * COLUMN_VALUE is oracle keyword + */ + COLUMN_VALUE("COLUMN_VALUE"), + /** + * COMMENT is oracle keyword + */ + COMMENT("COMMENT"), + /** + * COMPRESS is oracle keyword + */ + COMPRESS("COMPRESS"), + /** + * CONNECT is oracle keyword + */ + CONNECT("CONNECT"), + /** + * CREATE is oracle keyword + */ + CREATE("CREATE"), + /** + * CURRENT is oracle keyword + */ + CURRENT("CURRENT"), + /** + * DATE is oracle keyword + */ + DATE("DATE"), + /** + * DECIMAL is oracle keyword + */ + DECIMAL("DECIMAL"), + /** + * DEFAULT is oracle keyword + */ + DEFAULT("DEFAULT"), + /** + * DELETE is oracle keyword + */ + DELETE("DELETE"), + /** + * DESC is oracle keyword + */ + DESC("DESC"), + /** + * DISTINCT is oracle keyword + */ + DISTINCT("DISTINCT"), + /** + * DROP is oracle keyword + */ + DROP("DROP"), + /** + * ELSE is oracle keyword + */ + ELSE("ELSE"), + /** + * EXCLUSIVE is oracle keyword + */ + EXCLUSIVE("EXCLUSIVE"), + /** + * EXISTS is oracle keyword + */ + EXISTS("EXISTS"), + /** + * FILE is oracle keyword + */ + FILE("FILE"), + /** + * FLOAT is oracle keyword + */ + FLOAT("FLOAT"), + /** + * FOR is oracle keyword + */ + FOR("FOR"), + /** + * FROM is oracle keyword + */ + FROM("FROM"), + /** + * GRANT is oracle keyword + */ + GRANT("GRANT"), + /** + * GROUP is oracle keyword + */ + GROUP("GROUP"), + /** + * HAVING is oracle keyword + */ + HAVING("HAVING"), + /** + * IDENTIFIED is oracle keyword + */ + IDENTIFIED("IDENTIFIED"), + /** + * IMMEDIATE is oracle keyword + */ + IMMEDIATE("IMMEDIATE"), + /** + * IN is oracle keyword + */ + IN("IN"), + /** + * INCREMENT is oracle keyword + */ + INCREMENT("INCREMENT"), + /** + * INDEX is oracle keyword + */ + INDEX("INDEX"), + /** + * INITIAL is oracle keyword + */ + INITIAL("INITIAL"), + /** + * INSERT is oracle keyword + */ + INSERT("INSERT"), + /** + * INTEGER is oracle keyword + */ + INTEGER("INTEGER"), + /** + * INTERSECT is oracle keyword + */ + INTERSECT("INTERSECT"), + /** + * INTO is oracle keyword + */ + INTO("INTO"), + /** + * IS is oracle keyword + */ + IS("IS"), + /** + * LEVEL is oracle keyword + */ + LEVEL("LEVEL"), + /** + * LIKE is oracle keyword + */ + LIKE("LIKE"), + /** + * LOCK is oracle keyword + */ + LOCK("LOCK"), + /** + * LONG is oracle keyword + */ + LONG("LONG"), + /** + * MAXEXTENTS is oracle keyword + */ + MAXEXTENTS("MAXEXTENTS"), + /** + * MINUS is oracle keyword + */ + MINUS("MINUS"), + /** + * MLSLABEL is oracle keyword + */ + MLSLABEL("MLSLABEL"), + /** + * MODE is oracle keyword + */ + MODE("MODE"), + /** + * MODIFY is oracle keyword + */ + MODIFY("MODIFY"), + /** + * NESTED_TABLE_ID is oracle keyword + */ + NESTED_TABLE_ID("NESTED_TABLE_ID"), + /** + * NOAUDIT is oracle keyword + */ + NOAUDIT("NOAUDIT"), + /** + * NOCOMPRESS is oracle keyword + */ + NOCOMPRESS("NOCOMPRESS"), + /** + * NOT is oracle keyword + */ + NOT("NOT"), + /** + * NOWAIT is oracle keyword + */ + NOWAIT("NOWAIT"), + /** + * NULL is oracle keyword + */ + NULL("NULL"), + /** + * NUMBER is oracle keyword + */ + NUMBER("NUMBER"), + /** + * OF is oracle keyword + */ + OF("OF"), + /** + * OFFLINE is oracle keyword + */ + OFFLINE("OFFLINE"), + /** + * ON is oracle keyword + */ + ON("ON"), + /** + * ONLINE is oracle keyword + */ + ONLINE("ONLINE"), + /** + * OPTION is oracle keyword + */ + OPTION("OPTION"), + /** + * OR is oracle keyword + */ + OR("OR"), + /** + * ORDER is oracle keyword + */ + ORDER("ORDER"), + /** + * PCTFREE is oracle keyword + */ + PCTFREE("PCTFREE"), + /** + * PRIOR is oracle keyword + */ + PRIOR("PRIOR"), + /** + * PUBLIC is oracle keyword + */ + PUBLIC("PUBLIC"), + /** + * RAW is oracle keyword + */ + RAW("RAW"), + /** + * RENAME is oracle keyword + */ + RENAME("RENAME"), + /** + * RESOURCE is oracle keyword + */ + RESOURCE("RESOURCE"), + /** + * REVOKE is oracle keyword + */ + REVOKE("REVOKE"), + /** + * ROW is oracle keyword + */ + ROW("ROW"), + /** + * ROWID is oracle keyword + */ + ROWID("ROWID"), + /** + * ROWNUM is oracle keyword + */ + ROWNUM("ROWNUM"), + /** + * ROWS is oracle keyword + */ + ROWS("ROWS"), + /** + * SELECT is oracle keyword + */ + SELECT("SELECT"), + /** + * SESSION is oracle keyword + */ + SESSION("SESSION"), + /** + * SET is oracle keyword + */ + SET("SET"), + /** + * SHARE is oracle keyword + */ + SHARE("SHARE"), + /** + * SIZE is oracle keyword + */ + SIZE("SIZE"), + /** + * SMALLINT is oracle keyword + */ + SMALLINT("SMALLINT"), + /** + * START is oracle keyword + */ + START("START"), + /** + * SUCCESSFUL is oracle keyword + */ + SUCCESSFUL("SUCCESSFUL"), + /** + * SYNONYM is oracle keyword + */ + SYNONYM("SYNONYM"), + /** + * SYSDATE is oracle keyword + */ + SYSDATE("SYSDATE"), + /** + * TABLE is oracle keyword + */ + TABLE("TABLE"), + /** + * THEN is oracle keyword + */ + THEN("THEN"), + /** + * TO is oracle keyword + */ + TO("TO"), + /** + * TRIGGER is oracle keyword + */ + TRIGGER("TRIGGER"), + /** + * UID is oracle keyword + */ + UID("UID"), + /** + * UNION is oracle keyword + */ + UNION("UNION"), + /** + * UNIQUE is oracle keyword + */ + UNIQUE("UNIQUE"), + /** + * UPDATE is oracle keyword + */ + UPDATE("UPDATE"), + /** + * USER is oracle keyword + */ + USER("USER"), + /** + * VALIDATE is oracle keyword + */ + VALIDATE("VALIDATE"), + /** + * VALUES is oracle keyword + */ + VALUES("VALUES"), + /** + * VARCHAR is oracle keyword + */ + VARCHAR("VARCHAR"), + /** + * VARCHAR2 is oracle keyword + */ + VARCHAR2("VARCHAR2"), + /** + * VIEW is oracle keyword + */ + VIEW("VIEW"), + /** + * WHENEVER is oracle keyword + */ + WHENEVER("WHENEVER"), + /** + * WHERE is oracle keyword + */ + WHERE("WHERE"), + /** + * WITH is oracle keyword + */ + WITH("WITH"); + /** + * The Name. + */ + public final String name; + + OracleKeyword(String name) { + this.name = name; + } + } + + @Override + public boolean check(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + @Override + public boolean checkEscape(String fieldOrTableName) { + boolean check = check(fieldOrTableName); + // oracle + // we are recommend table name and column name must uppercase. + // if exists full uppercase, the table name or column name does't bundle escape symbol. + if (!check && isUppercase(fieldOrTableName)) { + return false; + } + return true; + } + + private static boolean isUppercase(String fieldOrTableName) { + if (fieldOrTableName == null) { + return false; + } + char[] chars = fieldOrTableName.toCharArray(); + for (char ch : chars) { + if (ch >= 'a' && ch <= 'z') { + return false; + } + } + return true; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java new file mode 100644 index 0000000..a542d1e --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParser.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.serializer.SimplePropertyPreFilter; +import io.seata.common.Constants; +import io.seata.common.executor.Initialize; +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; + +/** + * The type Json based undo log parser. + * + * @author sharajava + */ +@LoadLevel(name = FastjsonUndoLogParser.NAME) +public class FastjsonUndoLogParser implements UndoLogParser, Initialize { + + public static final String NAME = "fastjson"; + + private final SimplePropertyPreFilter filter = new SimplePropertyPreFilter(); + + @Override + public void init() { + filter.getExcludes().add("tableMeta"); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public byte[] getDefaultContent() { + return "{}".getBytes(Constants.DEFAULT_CHARSET); + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + String json = JSON.toJSONString(branchUndoLog, filter, SerializerFeature.WriteClassName, SerializerFeature.WriteDateUseDateFormat); + return json.getBytes(Constants.DEFAULT_CHARSET); + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + String text = new String(bytes, Constants.DEFAULT_CHARSET); + return JSON.parseObject(text, BranchUndoLog.class); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstSerializerFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstSerializerFactory.java new file mode 100644 index 0000000..5006ec1 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstSerializerFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.sql.Timestamp; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import org.nustaq.serialization.FSTConfiguration; +import org.nustaq.serialization.FSTObjectSerializer; + +/** + * @author funkye + */ +public class FstSerializerFactory { + + private static final FstSerializerFactory FACTORY = new FstSerializerFactory(); + + private final FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(); + + public static FstSerializerFactory getDefaultFactory() { + return FACTORY; + } + + public FstSerializerFactory() { + // support clob and blob sql type + conf.registerClass(SerialBlob.class, SerialClob.class, Timestamp.class); + UndoLogSerializerClassRegistry.getRegisteredClasses().keySet().forEach(conf::registerClass); + } + + public void registerSerializer(Class type, FSTObjectSerializer ser, boolean alsoForAllSubclasses) { + conf.registerSerializer(type, ser, alsoForAllSubclasses); + } + + public byte[] serialize(T t) { + return conf.asByteArray(t); + } + + public T deserialize(byte[] bytes) { + return (T)conf.asObject(bytes); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstUndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstUndoLogParser.java new file mode 100644 index 0000000..0231db2 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/FstUndoLogParser.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import io.seata.common.executor.Initialize; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.undo.parser.spi.FstSerializer; +import org.nustaq.serialization.FSTObjectSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * fst serializer + * @author funkye + */ +@LoadLevel(name = FstUndoLogParser.NAME) +public class FstUndoLogParser implements UndoLogParser, Initialize { + + private static final Logger LOGGER = LoggerFactory.getLogger(FstUndoLogParser.class); + + public static final String NAME = "fst"; + + private FstSerializerFactory fstFactory = FstSerializerFactory.getDefaultFactory(); + + @Override + public void init() { + try { + List serializers = EnhancedServiceLoader.loadAll(FstSerializer.class); + if (CollectionUtils.isNotEmpty(serializers)) { + for (FstSerializer serializer : serializers) { + if (serializer != null) { + Class type = serializer.type(); + FSTObjectSerializer ser = serializer.ser(); + boolean alsoForAllSubclasses = serializer.alsoForAllSubclasses(); + if (type != null && ser != null) { + fstFactory.registerSerializer(type, ser, alsoForAllSubclasses); + LOGGER.info("fst undo log parser load [{}].", serializer.getClass().getName()); + } + } + } + } + } catch (EnhancedServiceNotFoundException e) { + LOGGER.warn("FstSerializer not found children class.", e); + } + } + + @Override + public String getName() { + return NAME; + } + + @Override + public byte[] getDefaultContent() { + return fstFactory.serialize(new BranchUndoLog()); + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + return fstFactory.serialize(branchUndoLog); + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + return fstFactory.deserialize(bytes); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java new file mode 100644 index 0000000..9511126 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParser.java @@ -0,0 +1,304 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.util.Arrays; +import java.io.IOException; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import javax.sql.rowset.serial.SerialException; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.type.WritableTypeId; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.ser.std.ArraySerializerBase; +import io.seata.common.Constants; +import io.seata.common.executor.Initialize; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.undo.parser.spi.JacksonSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type Json based undo log parser. + * + * @author jsbxyyx + */ +@LoadLevel(name = JacksonUndoLogParser.NAME) +public class JacksonUndoLogParser implements UndoLogParser, Initialize { + + public static final String NAME = "jackson"; + + private static final Logger LOGGER = LoggerFactory.getLogger(JacksonUndoLogParser.class); + + private final ObjectMapper mapper = new ObjectMapper(); + + private final SimpleModule module = new SimpleModule(); + + /** + * customize serializer for java.sql.Timestamp + */ + private final JsonSerializer timestampSerializer = new TimestampSerializer(); + + /** + * customize deserializer for java.sql.Timestamp + */ + private final JsonDeserializer timestampDeserializer = new TimestampDeserializer(); + + /** + * customize serializer of java.sql.Blob + */ + private final JsonSerializer blobSerializer = new BlobSerializer(); + + /** + * customize deserializer of java.sql.Blob + */ + private final JsonDeserializer blobDeserializer = new BlobDeserializer(); + + /** + * customize serializer of java.sql.Clob + */ + private final JsonSerializer clobSerializer = new ClobSerializer(); + + /** + * customize deserializer of java.sql.Clob + */ + private final JsonDeserializer clobDeserializer = new ClobDeserializer(); + + @Override + public void init() { + try { + List jacksonSerializers = EnhancedServiceLoader.loadAll(JacksonSerializer.class); + if (CollectionUtils.isNotEmpty(jacksonSerializers)) { + for (JacksonSerializer jacksonSerializer : jacksonSerializers) { + Class type = jacksonSerializer.type(); + JsonSerializer ser = jacksonSerializer.ser(); + JsonDeserializer deser = jacksonSerializer.deser(); + if (type != null) { + if (ser != null) { + module.addSerializer(type, ser); + } + if (deser != null) { + module.addDeserializer(type, deser); + } + LOGGER.info("jackson undo log parser load [{}].", jacksonSerializer.getClass().getName()); + } + } + } + } catch (EnhancedServiceNotFoundException e) { + LOGGER.warn("JacksonSerializer not found children class.", e); + } + + module.addSerializer(Timestamp.class, timestampSerializer); + module.addDeserializer(Timestamp.class, timestampDeserializer); + module.addSerializer(SerialBlob.class, blobSerializer); + module.addDeserializer(SerialBlob.class, blobDeserializer); + module.addSerializer(SerialClob.class, clobSerializer); + module.addDeserializer(SerialClob.class, clobDeserializer); + mapper.registerModule(module); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + mapper.enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public byte[] getDefaultContent() { + return "{}".getBytes(Constants.DEFAULT_CHARSET); + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + try { + return mapper.writeValueAsBytes(branchUndoLog); + } catch (JsonProcessingException e) { + LOGGER.error("json encode exception, {}", e.getMessage(), e); + throw new RuntimeException(e); + } + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + try { + BranchUndoLog branchUndoLog; + if (Arrays.equals(bytes, getDefaultContent())) { + branchUndoLog = new BranchUndoLog(); + } else { + branchUndoLog = mapper.readValue(bytes, BranchUndoLog.class); + } + return branchUndoLog; + } catch (IOException e) { + LOGGER.error("json decode exception, {}", e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * if necessary + * extend {@link ArraySerializerBase} + */ + private static class TimestampSerializer extends JsonSerializer { + + @Override + public void serializeWithType(Timestamp timestamp, JsonGenerator gen, SerializerProvider serializers, + TypeSerializer typeSerializer) throws IOException { + WritableTypeId typeId = typeSerializer.writeTypePrefix(gen, + typeSerializer.typeId(timestamp, JsonToken.START_ARRAY)); + serialize(timestamp, gen, serializers); + gen.writeTypeSuffix(typeId); + } + + @Override + public void serialize(Timestamp timestamp, JsonGenerator gen, SerializerProvider serializers) { + try { + gen.writeNumber(timestamp.getTime()); + gen.writeNumber(timestamp.getNanos()); + } catch (IOException e) { + LOGGER.error("serialize java.sql.Timestamp error : {}", e.getMessage(), e); + } + + } + } + + /** + * if necessary + * extend {@link JsonNodeDeserializer} + */ + private static class TimestampDeserializer extends JsonDeserializer { + + @Override + public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) { + if (p.isExpectedStartArrayToken()) { + ArrayNode arrayNode; + try { + arrayNode = p.getCodec().readTree(p); + Timestamp timestamp = new Timestamp(arrayNode.get(0).asLong()); + timestamp.setNanos(arrayNode.get(1).asInt()); + return timestamp; + } catch (IOException e) { + LOGGER.error("deserialize java.sql.Timestamp error : {}", e.getMessage(), e); + } + } + LOGGER.error("deserialize java.sql.Timestamp type error."); + return null; + } + } + + /** + * the class of serialize blob type + */ + private static class BlobSerializer extends JsonSerializer { + + @Override + public void serializeWithType(SerialBlob blob, JsonGenerator gen, SerializerProvider serializers, + TypeSerializer typeSer) throws IOException { + WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, + typeSer.typeId(blob, JsonToken.VALUE_EMBEDDED_OBJECT)); + serialize(blob, gen, serializers); + typeSer.writeTypeSuffix(gen, typeIdDef); + } + + @Override + public void serialize(SerialBlob blob, JsonGenerator gen, SerializerProvider serializers) throws IOException { + try { + gen.writeBinary(blob.getBytes(1, (int)blob.length())); + } catch (SerialException e) { + LOGGER.error("serialize java.sql.Blob error : {}", e.getMessage(), e); + } + } + } + + /** + * the class of deserialize blob type + */ + private static class BlobDeserializer extends JsonDeserializer { + + @Override + public SerialBlob deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + try { + return new SerialBlob(p.getBinaryValue()); + } catch (SQLException e) { + LOGGER.error("deserialize java.sql.Blob error : {}", e.getMessage(), e); + } + return null; + } + } + + /** + * the class of serialize clob type + */ + private static class ClobSerializer extends JsonSerializer { + + @Override + public void serializeWithType(SerialClob clob, JsonGenerator gen, SerializerProvider serializers, + TypeSerializer typeSer) throws IOException { + WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, + typeSer.typeId(clob, JsonToken.VALUE_EMBEDDED_OBJECT)); + serialize(clob, gen, serializers); + typeSer.writeTypeSuffix(gen, typeIdDef); + } + + @Override + public void serialize(SerialClob clob, JsonGenerator gen, SerializerProvider serializers) throws IOException { + try { + gen.writeString(clob.getCharacterStream(), (int)clob.length()); + } catch (SerialException e) { + LOGGER.error("serialize java.sql.Blob error : {}", e.getMessage(), e); + } + } + } + + private static class ClobDeserializer extends JsonDeserializer { + + @Override + public SerialClob deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + try { + return new SerialClob(p.getValueAsString().toCharArray()); + + } catch (SQLException e) { + LOGGER.error("deserialize java.sql.Clob error : {}", e.getMessage(), e); + } + return null; + } + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializer.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializer.java new file mode 100644 index 0000000..868d99b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializer.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Objects; + +/** + * @author jsbxyyx + */ +public class KryoSerializer { + + private final Kryo kryo; + + public KryoSerializer(Kryo kryo) { + this.kryo = Objects.requireNonNull(kryo); + } + + public Kryo getKryo() { + return kryo; + } + + public byte[] serialize(T t) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Output output = new Output(baos); + kryo.writeClassAndObject(output, t); + output.close(); + return baos.toByteArray(); + } + + public T deserialize(byte[] bytes) { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + Input input = new Input(bais); + input.close(); + return (T) kryo.readClassAndObject(input); + } + +} \ No newline at end of file diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java new file mode 100644 index 0000000..6b56692 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoSerializerFactory.java @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.lang.reflect.InvocationHandler; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.pool.KryoFactory; +import com.esotericsoftware.kryo.pool.KryoPool; +import de.javakaffee.kryoserializers.JdkProxySerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jsbxyyx + */ +public class KryoSerializerFactory implements KryoFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(KryoSerializerFactory.class); + + private static final KryoSerializerFactory FACTORY = new KryoSerializerFactory(); + + private KryoPool pool = new KryoPool.Builder(this).softReferences().build(); + + private static final Map TYPE_MAP = new ConcurrentHashMap<>(); + + private KryoSerializerFactory() {} + + public static KryoSerializerFactory getInstance() { + return FACTORY; + } + + public KryoSerializer get() { + return new KryoSerializer(pool.borrow()); + } + + public void returnKryo(KryoSerializer kryoSerializer) { + if (kryoSerializer == null) { + throw new IllegalArgumentException("kryoSerializer is null"); + } + pool.release(kryoSerializer.getKryo()); + } + + public void registerSerializer(Class type, Serializer ser) { + if (type != null && ser != null) { + TYPE_MAP.put(type, ser); + } + } + + @Override + public Kryo create() { + Kryo kryo = new Kryo(); + kryo.setRegistrationRequired(false); + + for (Map.Entry entry : TYPE_MAP.entrySet()) { + kryo.register(entry.getKey(), entry.getValue()); + } + + // support clob and blob + kryo.register(SerialBlob.class, new BlobSerializer()); + kryo.register(SerialClob.class, new ClobSerializer()); + + // register sql type + kryo.register(Timestamp.class, new TimestampSerializer()); + kryo.register(InvocationHandler.class, new JdkProxySerializer()); + // register commonly class + UndoLogSerializerClassRegistry.getRegisteredClasses().forEach((clazz, ser) -> { + if (ser == null) { + kryo.register(clazz); + } else { + kryo.register(clazz, (Serializer)ser); + } + }); + return kryo; + } + + private static class BlobSerializer extends Serializer { + + @Override + public void write(Kryo kryo, Output output, Blob object) { + try { + byte[] bytes = object.getBytes(1L, (int)object.length()); + output.writeInt(bytes.length, true); + output.write(bytes); + } catch (SQLException e) { + LOGGER.error("kryo write java.sql.Blob error: {}", e.getMessage(), e); + } + } + + @Override + public Blob read(Kryo kryo, Input input, Class type) { + int length = input.readInt(true); + byte[] bytes = input.readBytes(length); + try { + return new SerialBlob(bytes); + } catch (SQLException e) { + LOGGER.error("kryo read java.sql.Blob error: {}", e.getMessage(), e); + } + return null; + } + + } + + private static class ClobSerializer extends Serializer { + + @Override + public void write(Kryo kryo, Output output, Clob object) { + try { + String s = object.getSubString(1, (int)object.length()); + output.writeString(s); + } catch (SQLException e) { + LOGGER.error("kryo write java.sql.Clob error: {}", e.getMessage(), e); + } + } + + @Override + public Clob read(Kryo kryo, Input input, Class type) { + try { + String s = input.readString(); + return new SerialClob(s.toCharArray()); + } catch (SQLException e) { + LOGGER.error("kryo read java.sql.Clob error: {}", e.getMessage(), e); + } + return null; + } + + } + + private class TimestampSerializer extends Serializer { + @Override + public void write(Kryo kryo, Output output, Timestamp object) { + output.writeLong(object.getTime(), true); + output.writeInt(object.getNanos(), true); + } + + @Override + public Timestamp read(Kryo kryo, Input input, Class type) { + Timestamp timestamp = new Timestamp(input.readLong(true)); + timestamp.setNanos(input.readInt(true)); + return timestamp; + } + } +} \ No newline at end of file diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParser.java new file mode 100644 index 0000000..2ab834e --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import com.esotericsoftware.kryo.Serializer; +import io.seata.common.executor.Initialize; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.undo.parser.spi.KryoTypeSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * kryo serializer + * @author jsbxyyx + */ +@LoadLevel(name = KryoUndoLogParser.NAME) +public class KryoUndoLogParser implements UndoLogParser, Initialize { + + private static final Logger LOGGER = LoggerFactory.getLogger(KryoUndoLogParser.class); + + public static final String NAME = "kryo"; + + @Override + public void init() { + try { + List serializers = EnhancedServiceLoader.loadAll(KryoTypeSerializer.class); + if (CollectionUtils.isNotEmpty(serializers)) { + for (KryoTypeSerializer typeSerializer : serializers) { + if (typeSerializer != null) { + Class type = typeSerializer.type(); + Serializer ser = typeSerializer.serializer(); + if (type != null) { + KryoSerializerFactory.getInstance().registerSerializer(type, ser); + LOGGER.info("kryo undo log parser load [{}].", typeSerializer.getClass().getName()); + } + } + } + } + } catch (EnhancedServiceNotFoundException e) { + LOGGER.warn("KryoTypeSerializer not found children class.", e); + } + } + + @Override + public String getName() { + return NAME; + } + + @Override + public byte[] getDefaultContent() { + KryoSerializer kryoSerializer = KryoSerializerFactory.getInstance().get(); + try { + return kryoSerializer.serialize(new BranchUndoLog()); + } finally { + KryoSerializerFactory.getInstance().returnKryo(kryoSerializer); + } + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + KryoSerializer kryoSerializer = KryoSerializerFactory.getInstance().get(); + try { + return kryoSerializer.serialize(branchUndoLog); + } finally { + KryoSerializerFactory.getInstance().returnKryo(kryoSerializer); + } + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + KryoSerializer kryoSerializer = KryoSerializerFactory.getInstance().get(); + try { + return kryoSerializer.deserialize(bytes); + } finally { + KryoSerializerFactory.getInstance().returnKryo(kryoSerializer); + } + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParser.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParser.java new file mode 100644 index 0000000..6df392f --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParser.java @@ -0,0 +1,254 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.util.List; + +import io.protostuff.Input; +import io.protostuff.LinkedBuffer; +import io.protostuff.Output; +import io.protostuff.Pipe; +import io.protostuff.ProtostuffIOUtil; +import io.protostuff.Schema; +import io.protostuff.WireFormat.FieldType; +import io.protostuff.runtime.DefaultIdStrategy; +import io.protostuff.runtime.Delegate; +import io.protostuff.runtime.RuntimeEnv; +import io.protostuff.runtime.RuntimeSchema; +import io.seata.common.executor.Initialize; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.undo.parser.spi.ProtostuffDelegate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The type protostuff based undo log parser. + * + * @author Geng Zhang + */ +@LoadLevel(name = ProtostuffUndoLogParser.NAME) +public class ProtostuffUndoLogParser implements UndoLogParser, Initialize { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProtostuffUndoLogParser.class); + + public static final String NAME = "protostuff"; + + private final DefaultIdStrategy idStrategy = (DefaultIdStrategy) RuntimeEnv.ID_STRATEGY; + + private final Schema schema = RuntimeSchema.getSchema(BranchUndoLog.class, idStrategy); + + @Override + public void init() { + try { + List delegates = EnhancedServiceLoader.loadAll(ProtostuffDelegate.class); + if (CollectionUtils.isNotEmpty(delegates)) { + for (ProtostuffDelegate delegate : delegates) { + idStrategy.registerDelegate(delegate.create()); + LOGGER.info("protostuff undo log parser load [{}].", delegate.getClass().getName()); + } + } + } catch (EnhancedServiceNotFoundException e) { + LOGGER.warn("ProtostuffDelegate not found children class.", e); + } + + idStrategy.registerDelegate(new DateDelegate()); + idStrategy.registerDelegate(new TimestampDelegate()); + idStrategy.registerDelegate(new SqlDateDelegate()); + idStrategy.registerDelegate(new TimeDelegate()); + } + + @Override + public String getName() { + return ProtostuffUndoLogParser.NAME; + } + + @Override + public byte[] getDefaultContent() { + return encode(new BranchUndoLog()); + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + // Re-use (manage) this buffer to avoid allocating on every serialization + LinkedBuffer buffer = LinkedBuffer.allocate(512); + // ser + try { + return ProtostuffIOUtil.toByteArray(branchUndoLog, schema, buffer); + } finally { + buffer.clear(); + } + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + if (bytes.length == 0) { + return new BranchUndoLog(); + } + BranchUndoLog fooParsed = schema.newMessage(); + ProtostuffIOUtil.mergeFrom(bytes, fooParsed, schema); + return fooParsed; + } + + /** + * Delegate for java.sql.Timestamp + * + * @author zhangsen + */ + public static class TimestampDelegate implements Delegate { + + @Override + public FieldType getFieldType() { + return FieldType.BYTES; + } + + @Override + public Class typeClass() { + return java.sql.Timestamp.class; + } + + @Override + public java.sql.Timestamp readFrom(Input input) throws IOException { + ByteBuffer buffer = input.readByteBuffer(); + long time = buffer.getLong(); + int nanos = buffer.getInt(); + buffer.flip(); + java.sql.Timestamp timestamp = new Timestamp(time); + timestamp.setNanos(nanos); + return timestamp; + } + + @Override + public void writeTo(Output output, int number, java.sql.Timestamp value, boolean repeated) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(12); + buffer.putLong(value.getTime()); + buffer.putInt(value.getNanos()); + buffer.flip(); + output.writeBytes(number, buffer, repeated); + } + + @Override + public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException { + output.writeBytes(number, input.readByteBuffer(), repeated); + } + } + + /** + * Delegate for java.sql.Date + * + * @author zhangsen + */ + public static class SqlDateDelegate implements Delegate { + + @Override + public FieldType getFieldType() { + return FieldType.FIXED64; + } + + @Override + public Class typeClass() { + return java.sql.Date.class; + } + + @Override + public java.sql.Date readFrom(Input input) throws IOException { + return new java.sql.Date(input.readFixed64()); + } + + @Override + public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException { + output.writeFixed64(number, input.readFixed64(), repeated); + } + + @Override + public void writeTo(Output output, int number, java.sql.Date value, boolean repeated) throws IOException { + output.writeFixed64(number, value.getTime(), repeated); + } + } + + /** + * Delegate for java.sql.Time + * + * @author zhangsen + */ + public static class TimeDelegate implements Delegate { + + @Override + public FieldType getFieldType() { + return FieldType.FIXED64; + } + + @Override + public Class typeClass() { + return java.sql.Time.class; + } + + @Override + public java.sql.Time readFrom(Input input) throws IOException { + return new java.sql.Time(input.readFixed64()); + } + + @Override + public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException { + output.writeFixed64(number, input.readFixed64(), repeated); + } + + @Override + public void writeTo(Output output, int number, java.sql.Time value, boolean repeated) throws IOException { + output.writeFixed64(number, value.getTime(), repeated); + } + } + + /** + * Delegate for java.util.Date + * + * @author zhangsen + */ + public static class DateDelegate implements Delegate { + + @Override + public FieldType getFieldType() { + return FieldType.FIXED64; + } + + @Override + public Class typeClass() { + return java.util.Date.class; + } + + @Override + public java.util.Date readFrom(Input input) throws IOException { + return new java.util.Date(input.readFixed64()); + } + + @Override + public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException { + output.writeFixed64(number, input.readFixed64(), repeated); + } + + @Override + public void writeTo(Output output, int number, java.util.Date value, boolean repeated) throws IOException { + output.writeFixed64(number, value.getTime(), repeated); + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/UndoLogSerializerClassRegistry.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/UndoLogSerializerClassRegistry.java new file mode 100644 index 0000000..445a961 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/UndoLogSerializerClassRegistry.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeSet; +import java.util.UUID; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import com.esotericsoftware.kryo.serializers.DefaultSerializers; +import de.javakaffee.kryoserializers.ArraysAsListSerializer; +import de.javakaffee.kryoserializers.BitSetSerializer; +import de.javakaffee.kryoserializers.GregorianCalendarSerializer; +import de.javakaffee.kryoserializers.RegexSerializer; +import de.javakaffee.kryoserializers.URISerializer; +import de.javakaffee.kryoserializers.UUIDSerializer; +import io.seata.rm.datasource.undo.BranchUndoLog; + +/** + * Provide a unified serialization registry, this class used for {@code seata-serializer-fst} + * and {@code seata-serializer-kryo}, it will register some classes at startup time (for example {@link KryoSerializerFactory#create}) + * @author funkye + */ +public class UndoLogSerializerClassRegistry { + + private static final Map, Object> REGISTRATIONS = new LinkedHashMap<>(); + + static { + // register serializer + registerClass(Collections.singletonList("").getClass(), new ArraysAsListSerializer()); + registerClass(GregorianCalendar.class, new GregorianCalendarSerializer()); + registerClass(BigDecimal.class, new DefaultSerializers.BigDecimalSerializer()); + registerClass(BigInteger.class, new DefaultSerializers.BigIntegerSerializer()); + registerClass(Pattern.class, new RegexSerializer()); + registerClass(BitSet.class, new BitSetSerializer()); + registerClass(URI.class, new URISerializer()); + registerClass(UUID.class, new UUIDSerializer()); + + // register commonly class + registerClass(HashMap.class); + registerClass(ArrayList.class); + registerClass(LinkedList.class); + registerClass(HashSet.class); + registerClass(TreeSet.class); + registerClass(Hashtable.class); + registerClass(Date.class); + registerClass(Calendar.class); + registerClass(ConcurrentHashMap.class); + registerClass(SimpleDateFormat.class); + registerClass(GregorianCalendar.class); + registerClass(Vector.class); + registerClass(BitSet.class); + registerClass(StringBuffer.class); + registerClass(StringBuilder.class); + registerClass(Object.class); + registerClass(Object[].class); + registerClass(String[].class); + registerClass(byte[].class); + registerClass(char[].class); + registerClass(int[].class); + registerClass(float[].class); + registerClass(double[].class); + + // register branchUndoLog + registerClass(BranchUndoLog.class); + } + + /** + * only supposed to be called at startup time + * + * @param clazz object type + */ + public static void registerClass(Class clazz) { + registerClass(clazz, null); + } + + /** + * only supposed to be called at startup time + * + * @param clazz object type + * @param serializer object serializer + */ + public static void registerClass(Class clazz, Object serializer) { + if (clazz == null) { + throw new IllegalArgumentException("Class registered cannot be null!"); + } + REGISTRATIONS.put(clazz, serializer); + } + + /** + * get registered classes + * + * @return class serializer + * */ + public static Map, Object> getRegisteredClasses() { + return REGISTRATIONS; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/FstSerializer.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/FstSerializer.java new file mode 100644 index 0000000..f090c9c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/FstSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.rm.datasource.undo.parser.spi; + +import org.nustaq.serialization.FSTObjectSerializer; + +/** + * The interface Fst serializer. + * + * @author jsbxyyx + */ +public interface FstSerializer { + + /** + * fst serializer class type + * + * @return class + */ + Class type(); + + /** + * FSTObjectSerializer custom serializer + * + * @return fst object serializer + */ + FSTObjectSerializer ser(); + + /** + * for sub classes + * + * @return boolean + */ + boolean alsoForAllSubclasses(); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/JacksonSerializer.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/JacksonSerializer.java new file mode 100644 index 0000000..1b70282 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/JacksonSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.rm.datasource.undo.parser.spi; + +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; + +/** + * The interface Jackson serializer. + * + * @param the type parameter + * @author jsbxyyx + */ +public interface JacksonSerializer { + + /** + * jackson serializer class type. + * + * @return class + */ + Class type(); + + /** + * Jackson custom serializer + * + * @return json serializer + */ + JsonSerializer ser(); + + /** + * Jackson custom deserializer + * + * @return json deserializer + */ + JsonDeserializer deser(); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/KryoTypeSerializer.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/KryoTypeSerializer.java new file mode 100644 index 0000000..ab14a0b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/KryoTypeSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.rm.datasource.undo.parser.spi; + +import com.esotericsoftware.kryo.Serializer; + +/** + * The interface Kryo type serializer. + * + * @param the type parameter + * @author jsbxyyx + */ +public interface KryoTypeSerializer { + + /** + * kryo serializer class type. + * + * @return class + */ + Class type(); + + /** + * kryo custom serializer. + * + * @return serializer + */ + Serializer serializer(); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/ProtostuffDelegate.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/ProtostuffDelegate.java new file mode 100644 index 0000000..518983c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/parser/spi/ProtostuffDelegate.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.rm.datasource.undo.parser.spi; + +import io.protostuff.runtime.Delegate; + +/** + * The interface Protostuff delegate. + * + * @param the type parameter + * @author jsbxyyx + */ +public interface ProtostuffDelegate { + + /** + * Delegate create. + * + * @return delegate + */ + Delegate create(); + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoDeleteExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoDeleteExecutor.java new file mode 100644 index 0000000..0ed7a34 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoDeleteExecutor.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type postgresql undo delete executor. + * + * @author japsercloud + */ +public class PostgresqlUndoDeleteExecutor extends AbstractUndoExecutor { + + /** + * Instantiates a new postgresql undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public PostgresqlUndoDeleteExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?) + */ + private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)"; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + List fields = new ArrayList<>(row.nonPrimaryKeys()); + fields.addAll(getOrderedPkList(beforeImage,row,JdbcConstants.POSTGRESQL)); + + // delete sql undo log before image all field come from table meta, need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String insertColumns = fields.stream() + .map(field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.POSTGRESQL)) + .collect(Collectors.joining(", ")); + String insertValues = fields.stream().map(field -> "?") + .collect(Collectors.joining(", ")); + + return String.format(INSERT_SQL_TEMPLATE, sqlUndoLog.getTableName(), insertColumns, insertValues); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoExecutorHolder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoExecutorHolder.java new file mode 100644 index 0000000..f516506 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoExecutorHolder.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoExecutorHolder; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The Type PostgresqlUndoExecutorHolder + * + * @author will + */ +@LoadLevel(name = JdbcConstants.POSTGRESQL) +public class PostgresqlUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new PostgresqlUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new PostgresqlUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new PostgresqlUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoInsertExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoInsertExecutor.java new file mode 100644 index 0000000..47fe4fa --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoInsertExecutor.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type postgresql undo insert executor. + * + * @author japsercloud + */ +public class PostgresqlUndoInsertExecutor extends AbstractUndoExecutor { + + /** + * DELETE FROM a WHERE pk = ? + */ + private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords afterImage = sqlUndoLog.getAfterImage(); + List afterImageRows = afterImage.getRows(); + if (CollectionUtils.isEmpty(afterImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + return generateDeleteSql(afterImageRows,afterImage); + } + + @Override + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) throws SQLException { + int undoIndex = 0; + for (Field pkField:pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + } + + private String generateDeleteSql(List rows, TableRecords afterImage) { + List pkNameList = getOrderedPkList(afterImage, rows.get(0), JdbcConstants.POSTGRESQL).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.POSTGRESQL); + return String.format(DELETE_SQL_TEMPLATE, sqlUndoLog.getTableName(), whereSql); + } + + /** + * Instantiates a new postgresql undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public PostgresqlUndoInsertExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoLogManager.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoLogManager.java new file mode 100644 index 0000000..0facf72 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoLogManager.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +import io.seata.common.loader.LoadLevel; +import io.seata.core.compressor.CompressorType; +import io.seata.core.constants.ClientTableColumnsName; +import io.seata.rm.datasource.undo.AbstractUndoLogManager; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author japsercloud + */ +@LoadLevel(name = JdbcConstants.POSTGRESQL) +public class PostgresqlUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlUndoLogManager.class); + + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_ID + "," + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + + ClientTableColumnsName.UNDO_LOG_XID + ", " + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + "VALUES (nextval('undo_log_id_seq'), ?, ?, ?, ?, ?, now(), now())"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + + ClientTableColumnsName.UNDO_LOG_ID + " IN (" + + "SELECT " + ClientTableColumnsName.UNDO_LOG_ID + " FROM " + UNDO_LOG_TABLE_NAME + + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= ? LIMIT ?" + ")"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + PreparedStatement deletePST = null; + try { + deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL); + deletePST.setDate(1, new java.sql.Date(logCreated.getTime())); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size " + deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } finally { + if (deletePST != null) { + deletePST.close(); + } + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchID, rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), + State.GlobalFinished, conn); + } + + private void insertUndoLog(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + PreparedStatement pst = null; + try { + pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL); + pst.setLong(1, branchID); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } finally { + if (pst != null) { + pst.close(); + } + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoUpdateExecutor.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoUpdateExecutor.java new file mode 100644 index 0000000..5c6488a --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/PostgresqlUndoUpdateExecutor.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql; + +import java.util.List; +import java.util.stream.Collectors; + +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.CollectionUtils; +import io.seata.rm.datasource.ColumnUtils; +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoExecutor; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * @author japsercloud + */ +public class PostgresqlUndoUpdateExecutor extends AbstractUndoExecutor { + + /** + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 = ? and pk2 = ? + */ + private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO + } + Row row = beforeImageRows.get(0); + + List nonPkFields = row.nonPrimaryKeys(); + // update sql undo log before image all field come from table meta. need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String updateColumns = nonPkFields.stream().map( + field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.POSTGRESQL) + " = ?").collect( + Collectors.joining(", ")); + + List pkNameList = getOrderedPkList(beforeImage, row, JdbcConstants.POSTGRESQL).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.POSTGRESQL); + + return String.format(UPDATE_SQL_TEMPLATE, sqlUndoLog.getTableName(), updateColumns, whereSql); + } + + /** + * Instantiates a new postgresql undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public PostgresqlUndoUpdateExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordChecker.java b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordChecker.java new file mode 100644 index 0000000..75abbad --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordChecker.java @@ -0,0 +1,395 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql.keyword; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * The type postgresql keyword checker. + * + * @author japsercloud + */ +@LoadLevel(name = JdbcConstants.POSTGRESQL) +public class PostgresqlKeywordChecker implements KeywordChecker { + + private Set keywordSet = Arrays.stream(PostgresqlKeywordChecker.PostgresqlKeyword.values()) + .map(PostgresqlKeywordChecker.PostgresqlKeyword::name).collect(Collectors.toSet()); + + /** + * postgresql keyword + */ + private enum PostgresqlKeyword { + /** + * ALL is postgresql keyword + */ + ALL("ALL"), + /** + * ANALYSE is postgresql keyword + */ + ANALYSE("ANALYSE"), + /** + * ANALYZE is postgresql keyword + */ + ANALYZE("ANALYZE"), + /** + * AND is postgresql keyword + */ + AND("AND"), + /** + * ANY is postgresql keyword + */ + ANY("ANY"), + /** + * ARRAY is postgresql keyword + */ + ARRAY("ARRAY"), + /** + * AS is postgresql keyword + */ + AS("AS"), + /** + * ASC is postgresql keyword + */ + ASC("ASC"), + /** + * ASYMMETRIC is postgresql keyword + */ + ASYMMETRIC("ASYMMETRIC"), + /** + * BOTH is postgresql keyword + */ + BOTH("BOTH"), + /** + * CASE is postgresql keyword + */ + CASE("CASE"), + /** + * CAST is postgresql keyword + */ + CAST("CAST"), + /** + * CHECK is postgresql keyword + */ + CHECK("CHECK"), + /** + * COLLATE is postgresql keyword + */ + COLLATE("COLLATE"), + /** + * COLUMN is postgresql keyword + */ + COLUMN("COLUMN"), + /** + * CONSTRAINT is postgresql keyword + */ + CONSTRAINT("CONSTRAINT"), + /** + * CREATE is postgresql keyword + */ + CREATE("CREATE"), + /** + * CURRENT_CATALOG is postgresql keyword + */ + CURRENT_CATALOG("CURRENT_CATALOG"), + /** + * CURRENT_DATE is postgresql keyword + */ + CURRENT_DATE("CURRENT_DATE"), + /** + * CURRENT_ROLE is postgresql keyword + */ + CURRENT_ROLE("CURRENT_ROLE"), + /** + * CURRENT_TIME is postgresql keyword + */ + CURRENT_TIME("CURRENT_TIME"), + /** + * CURRENT_TIMESTAMP is postgresql keyword + */ + CURRENT_TIMESTAMP("CURRENT_TIMESTAMP"), + /** + * CURRENT_USER is postgresql keyword + */ + CURRENT_USER("CURRENT_USER"), + /** + * DEFAULT is postgresql keyword + */ + DEFAULT("DEFAULT"), + /** + * DEFERRABLE is postgresql keyword + */ + DEFERRABLE("DEFERRABLE"), + /** + * DESC is postgresql keyword + */ + DESC("DESC"), + /** + * DISTINCT is postgresql keyword + */ + DISTINCT("DISTINCT"), + /** + * DO is postgresql keyword + */ + DO("DO"), + /** + * ELSE is postgresql keyword + */ + ELSE("ELSE"), + /** + * END is postgresql keyword + */ + END("END"), + /** + * EXCEPT is postgresql keyword + */ + EXCEPT("EXCEPT"), + /** + * FALSE is postgresql keyword + */ + FALSE("FALSE"), + /** + * FETCH is postgresql keyword + */ + FETCH("FETCH"), + /** + * FOR is postgresql keyword + */ + FOR("FOR"), + /** + * FOREIGN is postgresql keyword + */ + FOREIGN("FOREIGN"), + /** + * FROM is postgresql keyword + */ + FROM("FROM"), + /** + * GRANT is postgresql keyword + */ + GRANT("GRANT"), + /** + * GROUP is postgresql keyword + */ + GROUP("GROUP"), + /** + * HAVING is postgresql keyword + */ + HAVING("HAVING"), + /** + * IN is postgresql keyword + */ + IN("IN"), + /** + * INITIALLY is postgresql keyword + */ + INITIALLY("INITIALLY"), + /** + * INTERSECT is postgresql keyword + */ + INTERSECT("INTERSECT"), + /** + * INTO is postgresql keyword + */ + INTO("INTO"), + /** + * LATERAL is postgresql keyword + */ + LATERAL("LATERAL"), + /** + * LEADING is postgresql keyword + */ + LEADING("LEADING"), + /** + * LIMIT is postgresql keyword + */ + LIMIT("LIMIT"), + /** + * LOCALTIME is postgresql keyword + */ + LOCALTIME("LOCALTIME"), + /** + * LOCALTIMESTAMP is postgresql keyword + */ + LOCALTIMESTAMP("LOCALTIMESTAMP"), + /** + * NOT is postgresql keyword + */ + NOT("NOT"), + /** + * NULL is postgresql keyword + */ + NULL("NULL"), + /** + * OFFSET is postgresql keyword + */ + OFFSET("OFFSET"), + /** + * ON is postgresql keyword + */ + ON("ON"), + /** + * ONLY is postgresql keyword + */ + ONLY("ONLY"), + /** + * OR is postgresql keyword + */ + OR("OR"), + /** + * ORDER is postgresql keyword + */ + ORDER("ORDER"), + /** + * PLACING is postgresql keyword + */ + PLACING("PLACING"), + /** + * PRIMARY is postgresql keyword + */ + PRIMARY("PRIMARY"), + /** + * REFERENCES is postgresql keyword + */ + REFERENCES("REFERENCES"), + /** + * RETURNING is postgresql keyword + */ + RETURNING("RETURNING"), + /** + * SELECT is postgresql keyword + */ + SELECT("SELECT"), + /** + * SESSION_USER is postgresql keyword + */ + SESSION_USER("SESSION_USER"), + /** + * SOME is postgresql keyword + */ + SOME("SOME"), + /** + * SYMMETRIC is postgresql keyword + */ + SYMMETRIC("SYMMETRIC"), + /** + * TABLE is postgresql keyword + */ + TABLE("TABLE"), + /** + * THEN is postgresql keyword + */ + THEN("THEN"), + /** + * TO is postgresql keyword + */ + TO("TO"), + /** + * TRAILING is postgresql keyword + */ + TRAILING("TRAILING"), + /** + * TRUE is postgresql keyword + */ + TRUE("TRUE"), + /** + * UNION is postgresql keyword + */ + UNION("UNION"), + /** + * UNIQUE is postgresql keyword + */ + UNIQUE("UNIQUE"), + /** + * USER is postgresql keyword + */ + USER("USER"), + /** + * USING is postgresql keyword + */ + USING("USING"), + /** + * VARIADIC is postgresql keyword + */ + VARIADIC("VARIADIC"), + /** + * WHEN is postgresql keyword + */ + WHEN("WHEN"), + /** + * WHERE is postgresql keyword + */ + WHERE("WHERE"), + /** + * WINDOW is postgresql keyword + */ + WINDOW("WINDOW"), + /** + * WITH is postgresql keyword + */ + WITH("WITH"); + /** + * The Name. + */ + public final String name; + + PostgresqlKeyword(String name) { + this.name = name; + } + } + + @Override + public boolean check(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + @Override + public boolean checkEscape(String fieldOrTableName) { + boolean check = check(fieldOrTableName); + if (!check && !containsUppercase(fieldOrTableName)) { + // postgresql + // we are recommend table name and column name must lowercase. + // if exists uppercase character or full uppercase, the table name or column name must bundle escape symbol. + return false; + } + return true; + } + + private static boolean containsUppercase(String colName) { + if (colName == null) { + return false; + } + char[] chars = colName.toCharArray(); + for (char ch : chars) { + if (ch >= 'A' && ch <= 'Z') { + return true; + } + } + return false; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/util/JdbcUtils.java b/rm-datasource/src/main/java/io/seata/rm/datasource/util/JdbcUtils.java new file mode 100644 index 0000000..d41daf4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/util/JdbcUtils.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.util; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.rm.BaseDataSourceResource; +import io.seata.rm.DefaultResourceManager; +import io.seata.sqlparser.SqlParserType; +import io.seata.sqlparser.util.DbTypeParser; + +import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; + +/** + * @author ggndnn + * @author sharajava + */ +public final class JdbcUtils { + + private static volatile DbTypeParser dbTypeParser; + + static DbTypeParser getDbTypeParser() { + if (dbTypeParser == null) { + synchronized (JdbcUtils.class) { + if (dbTypeParser == null) { + String sqlparserType = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.SQL_PARSER_TYPE, SqlParserType.SQL_PARSER_TYPE_DRUID); + dbTypeParser = EnhancedServiceLoader.load(DbTypeParser.class, sqlparserType); + } + } + } + return dbTypeParser; + } + + private JdbcUtils() { + } + + public static String getDbType(String jdbcUrl) { + return getDbTypeParser().parseFromJdbcUrl(jdbcUrl).toLowerCase(); + } + + /** + * Init a DataSourceResource instance with DataSource instance and given resource group ID. + * + * @param dataSourceResource the DataSourceResource instance + * @param dataSource the DataSource instance + * @param resourceGroupId the given resource group ID + */ + public static void initDataSourceResource(BaseDataSourceResource dataSourceResource, DataSource dataSource, String resourceGroupId) { + dataSourceResource.setResourceGroupId(resourceGroupId); + try (Connection connection = dataSource.getConnection()) { + String jdbcUrl = connection.getMetaData().getURL(); + dataSourceResource.setResourceId(buildResourceId(jdbcUrl)); + String driverClassName = com.alibaba.druid.util.JdbcUtils.getDriverClassName(jdbcUrl); + dataSourceResource.setDriver(loadDriver(driverClassName)); + dataSourceResource.setDbType(com.alibaba.druid.util.JdbcUtils.getDbType(jdbcUrl, driverClassName)); + } catch (SQLException e) { + throw new IllegalStateException("can not init DataSourceResource with " + dataSource, e); + } + DefaultResourceManager.get().registerResource(dataSourceResource); + } + + public static void initXADataSourceResource(BaseDataSourceResource dataSourceResource, XADataSource dataSource, String resourceGroupId) { + dataSourceResource.setResourceGroupId(resourceGroupId); + try { + XAConnection xaConnection = dataSource.getXAConnection(); + try (Connection connection = xaConnection.getConnection()) { + String jdbcUrl = connection.getMetaData().getURL(); + dataSourceResource.setResourceId(buildResourceId(jdbcUrl)); + String driverClassName = com.alibaba.druid.util.JdbcUtils.getDriverClassName(jdbcUrl); + dataSourceResource.setDriver(loadDriver(driverClassName)); + dataSourceResource.setDbType(com.alibaba.druid.util.JdbcUtils.getDbType(jdbcUrl, driverClassName)); + } finally { + if (xaConnection != null) { + xaConnection.close(); + } + } + } catch (SQLException e) { + throw new IllegalStateException("can not get XAConnection from DataSourceResource with " + dataSource, e); + } + DefaultResourceManager.get().registerResource(dataSourceResource); + } + + public static String buildResourceId(String jdbcUrl) { + if (jdbcUrl.contains("?")) { + return jdbcUrl.substring(0, jdbcUrl.indexOf('?')); + } + return jdbcUrl; + } + + public static Driver loadDriver(String driverClassName) throws SQLException { + Class clazz = null; + try { + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + if (contextLoader != null) { + clazz = contextLoader.loadClass(driverClassName); + } + } catch (ClassNotFoundException e) { + // skip + } + + if (clazz == null) { + try { + clazz = Class.forName(driverClassName); + } catch (ClassNotFoundException e) { + throw new SQLException(e.getMessage(), e); + } + } + + try { + return (Driver)clazz.newInstance(); + } catch (IllegalAccessException e) { + throw new SQLException(e.getMessage(), e); + } catch (InstantiationException e) { + throw new SQLException(e.getMessage(), e); + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/util/XAUtils.java b/rm-datasource/src/main/java/io/seata/rm/datasource/util/XAUtils.java new file mode 100644 index 0000000..eca417c --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/util/XAUtils.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.util; + +import com.alibaba.druid.util.JdbcUtils; +import com.alibaba.druid.util.MySqlUtils; +import com.alibaba.druid.util.PGUtils; +import io.seata.rm.BaseDataSourceResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.XAConnection; +import javax.transaction.xa.XAException; +import java.lang.reflect.Constructor; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; + +public class XAUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(XAUtils.class); + + public static String getDbType(String jdbcUrl, String driverClassName) { + return JdbcUtils.getDbType(jdbcUrl, driverClassName); + } + + public static XAConnection createXAConnection(Connection physicalConn, BaseDataSourceResource dataSourceResource) throws SQLException { + return createXAConnection(physicalConn, dataSourceResource.getDriver(), dataSourceResource.getDbType()); + } + + public static XAConnection createXAConnection(Connection physicalConn, Driver driver, String dbType) throws SQLException { + if (JdbcUtils.ORACLE.equals(dbType)) { + try { + // https://github.com/alibaba/druid/issues/3707 + // before Druid issue fixed, just make ORACLE XA connection in my way. + // return OracleUtils.OracleXAConnection(physicalConn); + String physicalConnClassName = physicalConn.getClass().getName(); + if ("oracle.jdbc.driver.T4CConnection".equals(physicalConnClassName)) { + return createOracleXAConnection(physicalConn, "oracle.jdbc.driver.T4CXAConnection"); + } else { + return createOracleXAConnection(physicalConn, "oracle.jdbc.xa.client.OracleXAConnection"); + } + } catch (XAException xae) { + throw new SQLException("create xaConnection error", xae); + } + } + + if (JdbcUtils.MYSQL.equals(dbType) || JdbcUtils.MARIADB.equals(dbType)) { + return MySqlUtils.createXAConnection(driver, physicalConn); + } + + if (JdbcUtils.POSTGRESQL.equals(dbType)) { + return PGUtils.createXAConnection(physicalConn); + } + + throw new SQLException("xa not support dbType: " + dbType); + } + + private static XAConnection createOracleXAConnection(Connection physicalConnection, String xaConnectionClassName) throws XAException, SQLException { + try { + Class xaConnectionClass = Class.forName(xaConnectionClassName); + Constructor constructor = xaConnectionClass.getConstructor(Connection.class); + constructor.setAccessible(true); + return constructor.newInstance(physicalConnection); + } catch (Exception e) { + LOGGER.warn("Failed to create Oracle XA Connection " + xaConnectionClassName + " on " + physicalConnection); + if (e instanceof XAException) { + throw (XAException) e; + } else { + throw new SQLException(e); + } + } + + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractConnectionProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractConnectionProxyXA.java new file mode 100644 index 0000000..2aee173 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractConnectionProxyXA.java @@ -0,0 +1,351 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import io.seata.core.context.RootContext; +import io.seata.rm.BaseDataSourceResource; + +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; + +/** + * The type Abstract connection proxy on XA mode. + * + * @author sharajava + */ +public abstract class AbstractConnectionProxyXA implements Connection { + + public static final String SQLSTATE_XA_NOT_END = "SQLSTATE_XA_NOT_END"; + + protected Connection originalConnection; + + protected XAConnection xaConnection; + + protected XAResource xaResource; + + protected BaseDataSourceResource resource; + + protected String xid; + + public AbstractConnectionProxyXA(Connection originalConnection, XAConnection xaConnection, BaseDataSourceResource resource, String xid) { + this.originalConnection = originalConnection; + this.xaConnection = xaConnection; + this.resource = resource; + this.xid = xid; + } + + public XAConnection getWrappedXAConnection() { + return xaConnection; + } + + public Connection getWrappedConnection() { + return originalConnection; + } + + @Override + public Statement createStatement() throws SQLException { + Statement targetStatement = originalConnection.createStatement(); + return new StatementProxyXA(this, targetStatement); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + PreparedStatement targetStatement = originalConnection.prepareStatement(sql); + return new PreparedStatementProxyXA(this, targetStatement); + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return originalConnection.prepareCall(sql); + } + + @Override + public String nativeSQL(String sql) throws SQLException { + return originalConnection.nativeSQL(sql); + } + + @Override + public boolean isClosed() throws SQLException { + return originalConnection.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return originalConnection.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + originalConnection.setReadOnly(readOnly); + + } + + @Override + public boolean isReadOnly() throws SQLException { + return originalConnection.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + originalConnection.setCatalog(catalog); + + } + + @Override + public String getCatalog() throws SQLException { + return originalConnection.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + originalConnection.setTransactionIsolation(level); + + } + + @Override + public int getTransactionIsolation() throws SQLException { + return originalConnection.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return originalConnection.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + originalConnection.clearWarnings(); + + } + + @Override + public Map> getTypeMap() throws SQLException { + return originalConnection.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + originalConnection.setTypeMap(map); + + } + + @Override + public void setHoldability(int holdability) throws SQLException { + originalConnection.setHoldability(holdability); + + } + + @Override + public int getHoldability() throws SQLException { + return originalConnection.getHoldability(); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return originalConnection.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return originalConnection.setSavepoint(name); + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + originalConnection.rollback(savepoint); + + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + originalConnection.releaseSavepoint(savepoint); + + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + Statement statement = originalConnection.createStatement(resultSetType, resultSetConcurrency); + return new StatementProxyXA(this, statement); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + PreparedStatement preparedStatement = originalConnection.prepareStatement(sql, resultSetType, + resultSetConcurrency); + return new PreparedStatementProxyXA(this, preparedStatement); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return originalConnection.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + Statement statement = originalConnection.createStatement(resultSetType, resultSetConcurrency, + resultSetHoldability); + return new StatementProxyXA(this, statement); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + PreparedStatement preparedStatement = originalConnection.prepareStatement(sql, resultSetType, + resultSetConcurrency, resultSetHoldability); + return new PreparedStatementProxyXA(this, preparedStatement); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + RootContext.assertNotInGlobalTransaction(); + return originalConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + PreparedStatement preparedStatement = originalConnection.prepareStatement(sql, autoGeneratedKeys); + return new PreparedStatementProxyXA(this, preparedStatement); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + PreparedStatement preparedStatement = originalConnection.prepareStatement(sql, columnIndexes); + return new PreparedStatementProxyXA(this, preparedStatement); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + PreparedStatement preparedStatement = originalConnection.prepareStatement(sql, columnNames); + return new PreparedStatementProxyXA(this, preparedStatement); + } + + @Override + public Clob createClob() throws SQLException { + return originalConnection.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return originalConnection.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return originalConnection.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return originalConnection.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return originalConnection.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + originalConnection.setClientInfo(name, value); + + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + originalConnection.setClientInfo(properties); + + } + + @Override + public String getClientInfo(String name) throws SQLException { + return originalConnection.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return originalConnection.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return originalConnection.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return originalConnection.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + originalConnection.setSchema(schema); + + } + + @Override + public String getSchema() throws SQLException { + return originalConnection.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + originalConnection.abort(executor); + + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + originalConnection.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return originalConnection.getNetworkTimeout(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return originalConnection.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return originalConnection.isWrapperFor(iface); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractDataSourceProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractDataSourceProxyXA.java new file mode 100644 index 0000000..2f1a57b --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/AbstractDataSourceProxyXA.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import io.seata.rm.BaseDataSourceResource; + +import javax.sql.PooledConnection; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * Abstract DataSource proxy for XA mode. + * + * @author sharajava + */ +public abstract class AbstractDataSourceProxyXA extends BaseDataSourceResource { + + protected static final String DEFAULT_RESOURCE_GROUP_ID = "DEFAULT_XA"; + + /** + * Get a ConnectionProxyXA instance for finishing XA branch(XA commit/XA rollback) + * @return ConnectionProxyXA instance + * @throws SQLException exception + */ + public ConnectionProxyXA getConnectionForXAFinish(XAXid xaXid) throws SQLException { + ConnectionProxyXA connectionProxyXA = lookup(xaXid.toString()); + if (connectionProxyXA != null) { + return connectionProxyXA; + } + return (ConnectionProxyXA)getConnectionProxyXA(); + } + + protected abstract Connection getConnectionProxyXA() throws SQLException; + + /** + * Force close the physical connection kept for XA branch of given XAXid. + * @param xaXid the given XAXid + * @throws SQLException exception + */ + public void forceClosePhysicalConnection(XAXid xaXid) throws SQLException { + ConnectionProxyXA connectionProxyXA = lookup(xaXid.toString()); + if (connectionProxyXA != null) { + connectionProxyXA.close(); + Connection physicalConn = connectionProxyXA.getWrappedConnection(); + if (physicalConn instanceof PooledConnection) { + physicalConn = ((PooledConnection)physicalConn).getConnection(); + } + // Force close the physical connection + physicalConn.close(); + } + + + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ConnectionProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ConnectionProxyXA.java new file mode 100644 index 0000000..0533acc --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ConnectionProxyXA.java @@ -0,0 +1,266 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.XAConnection; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; + +import com.alibaba.druid.util.JdbcUtils; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.rm.BaseDataSourceResource; +import io.seata.rm.DefaultResourceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connection proxy for XA mode. + * + * @author sharajava + */ +public class ConnectionProxyXA extends AbstractConnectionProxyXA implements Holdable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionProxyXA.class); + + private boolean currentAutoCommitStatus = true; + + private XAXid xaBranchXid; + + private boolean xaActive = false; + + private boolean kept = false; + + /** + * Constructor of Connection Proxy for XA mode. + * + * @param originalConnection Normal Connection from the original DataSource. + * @param xaConnection XA Connection based on physical connection of the normal Connection above. + * @param resource The corresponding Resource(DataSource proxy) from which the connections was created. + * @param xid Seata global transaction xid. + */ + public ConnectionProxyXA(Connection originalConnection, XAConnection xaConnection, BaseDataSourceResource resource, String xid) { + super(originalConnection, xaConnection, resource, xid); + } + + public void init() { + try { + this.xaResource = xaConnection.getXAResource(); + this.currentAutoCommitStatus = this.originalConnection.getAutoCommit(); + if (!currentAutoCommitStatus) { + throw new IllegalStateException("Connection[autocommit=false] as default is NOT supported"); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + + } + + private void keepIfNecessary() { + if (shouldBeHeld()) { + resource.hold(xaBranchXid.toString(), this); + } + } + + private void releaseIfNecessary() { + if (isHeld()) { + resource.release(xaBranchXid.toString(), this); + } + } + + /** + * XA commit + * @param xid global transaction xid + * @param branchId transaction branch id + * @param applicationData application data + * @throws SQLException SQLException + */ + public void xaCommit(String xid, long branchId, String applicationData) throws XAException { + XAXid xaXid = XAXidBuilder.build(xid, branchId); + xaResource.commit(xaXid, false); + releaseIfNecessary(); + + } + + /** + * XA rollback + * @param xid global transaction xid + * @param branchId transaction branch id + * @param applicationData application data + * @throws SQLException SQLException + */ + public void xaRollback(String xid, long branchId, String applicationData) throws XAException { + XAXid xaXid = XAXidBuilder.build(xid, branchId); + xaRollback(xaXid); + } + + /** + * XA rollback + * @param xaXid xaXid + * @throws XAException XAException + */ + public void xaRollback(XAXid xaXid) throws XAException { + xaResource.rollback(xaXid); + releaseIfNecessary(); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + if (currentAutoCommitStatus == autoCommit) { + return; + } + if (autoCommit) { + // According to JDBC spec: + // If this method is called during a transaction and the + // auto-commit mode is changed, the transaction is committed. + if (xaActive) { + commit(); + } + } else { + if (xaActive) { + throw new SQLException("should NEVER happen: setAutoCommit from true to false while xa branch is active"); + } + // Start a XA branch + long branchId = 0L; + try { + // 1. register branch to TC then get the branchId + branchId = DefaultResourceManager.get().branchRegister(BranchType.XA, resource.getResourceId(), null, xid, null, + null); + } catch (TransactionException te) { + cleanXABranchContext(); + throw new SQLException("failed to register xa branch " + xid + " since " + te.getCode() + ":" + te.getMessage(), te); + } + // 2. build XA-Xid with xid and branchId + this.xaBranchXid = XAXidBuilder.build(xid, branchId); + try { + // 3. XA Start + xaResource.start(this.xaBranchXid, XAResource.TMNOFLAGS); + } catch (XAException e) { + cleanXABranchContext(); + throw new SQLException("failed to start xa branch " + xid + " since " + e.getMessage(), e); + } + // 4. XA is active + this.xaActive = true; + + } + + currentAutoCommitStatus = autoCommit; + } + + @Override + public boolean getAutoCommit() throws SQLException { + return currentAutoCommitStatus; + } + + @Override + public void commit() throws SQLException { + if (currentAutoCommitStatus) { + // Ignore the committing on an autocommit session. + return; + } + if (!xaActive || this.xaBranchXid == null) { + throw new SQLException("should NOT commit on an inactive session", SQLSTATE_XA_NOT_END); + } + try { + // XA End: Success + xaResource.end(xaBranchXid, XAResource.TMSUCCESS); + // XA Prepare + xaResource.prepare(xaBranchXid); + // Keep the Connection if necessary + keepIfNecessary(); + } catch (XAException xe) { + try { + // Branch Report to TC: Failed + DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(), + BranchStatus.PhaseOne_Failed, null); + } catch (TransactionException te) { + LOGGER.warn("Failed to report XA branch commit-failure on " + xid + "-" + xaBranchXid.getBranchId() + + " since " + te.getCode() + ":" + te.getMessage() + " and XAException:" + xe.getMessage()); + + } + throw new SQLException( + "Failed to end(TMSUCCESS)/prepare xa branch on " + xid + "-" + xaBranchXid.getBranchId() + " since " + xe + .getMessage(), xe); + } finally { + cleanXABranchContext(); + } + } + + @Override + public void rollback() throws SQLException { + if (currentAutoCommitStatus) { + // Ignore the committing on an autocommit session. + return; + } + if (!xaActive || this.xaBranchXid == null) { + throw new SQLException("should NOT rollback on an inactive session"); + } + try { + // XA End: Fail + xaResource.end(xaBranchXid, XAResource.TMFAIL); + xaRollback(xaBranchXid); + // Branch Report to TC + DefaultResourceManager.get().branchReport(BranchType.XA, xid, xaBranchXid.getBranchId(), + BranchStatus.PhaseOne_Failed, null); + LOGGER.info(xaBranchXid + " was rollbacked"); + } catch (XAException xe) { + throw new SQLException("Failed to end(TMFAIL) xa branch on " + xid + "-" + xaBranchXid.getBranchId() + + " since " + xe.getMessage(), xe); + } catch (TransactionException te) { + // log and ignore the report failure + LOGGER.warn("Failed to report XA branch rollback on " + xid + "-" + xaBranchXid.getBranchId() + " since " + + te.getCode() + ":" + te.getMessage()); + } finally { + cleanXABranchContext(); + } + } + + private void cleanXABranchContext() { + xaActive = false; + if (!isHeld()) { + xaBranchXid = null; + } + } + + @Override + public void close() throws SQLException { + if (isHeld()) { + // if kept by a keeper, just hold the connection. + return; + } + cleanXABranchContext(); + originalConnection.close(); + } + + @Override + public void setHeld(boolean kept) { + this.kept = kept; + } + + @Override + public boolean isHeld() { + return kept; + } + + @Override + public boolean shouldBeHeld() { + return JdbcUtils.MYSQL.equals(resource.getDbType()) || JdbcUtils.MARIADB.equals(resource.getDbType()); + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXA.java new file mode 100644 index 0000000..596f4f9 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXA.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import javax.sql.XAConnection; + +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.rm.datasource.SeataDataSourceProxy; +import io.seata.rm.datasource.util.JdbcUtils; +import io.seata.rm.datasource.util.XAUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * DataSource proxy for XA mode. + * + * @author sharajava + */ +public class DataSourceProxyXA extends AbstractDataSourceProxyXA { + + private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceProxyXA.class); + + public DataSourceProxyXA(DataSource dataSource) { + this(dataSource, DEFAULT_RESOURCE_GROUP_ID); + } + + public DataSourceProxyXA(DataSource dataSource, String resourceGroupId) { + if (dataSource instanceof SeataDataSourceProxy) { + LOGGER.info("Unwrap the data source, because the type is: {}", dataSource.getClass().getName()); + dataSource = ((SeataDataSourceProxy) dataSource).getTargetDataSource(); + } + this.dataSource = dataSource; + this.branchType = BranchType.XA; + JdbcUtils.initDataSourceResource(this, dataSource, resourceGroupId); + + //Set the default branch type to 'XA' in the RootContext. + RootContext.setDefaultBranchType(this.getBranchType()); + } + + @Override + public Connection getConnection() throws SQLException { + Connection connection = dataSource.getConnection(); + return getConnectionProxy(connection); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + Connection connection = dataSource.getConnection(username, password); + return getConnectionProxy(connection); + } + + protected Connection getConnectionProxy(Connection connection) throws SQLException { + if (!RootContext.inGlobalTransaction()) { + return connection; + } + return getConnectionProxyXA(connection); + } + + @Override + protected Connection getConnectionProxyXA() throws SQLException { + Connection connection = dataSource.getConnection(); + return getConnectionProxyXA(connection); + } + + private Connection getConnectionProxyXA(Connection connection) throws SQLException { + Connection physicalConn = connection.unwrap(Connection.class); + XAConnection xaConnection = XAUtils.createXAConnection(physicalConn, this); + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, this, RootContext.getXID()); + connectionProxyXA.init(); + return connectionProxyXA; + } + +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXANative.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXANative.java new file mode 100644 index 0000000..57b65d4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/DataSourceProxyXANative.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.rm.datasource.util.JdbcUtils; + +import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import java.sql.Connection; +import java.sql.SQLException; + +/** + * DataSource proxy to wrap a XADataSource. + * + * @author sharajava + */ +public class DataSourceProxyXANative extends AbstractDataSourceProxyXA { + + private XADataSource xaDataSource; + + public DataSourceProxyXANative(XADataSource dataSource) { + this(dataSource, DEFAULT_RESOURCE_GROUP_ID); + } + + public DataSourceProxyXANative(XADataSource dataSource, String resourceGroupId) { + if (dataSource instanceof DataSource) { + this.dataSource = (DataSource)dataSource; + } + this.xaDataSource = dataSource; + this.branchType = BranchType.XA; + JdbcUtils.initXADataSourceResource(this, dataSource, resourceGroupId); + } + + @Override + public Connection getConnection() throws SQLException { + XAConnection xaConnection = xaDataSource.getXAConnection(); + return getConnectionProxy(xaConnection); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + XAConnection xaConnection = xaDataSource.getXAConnection(username, password); + return getConnectionProxy(xaConnection); + } + + protected Connection getConnectionProxy(XAConnection xaConnection) throws SQLException { + Connection connection = xaConnection.getConnection(); + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, this, RootContext.getXID()); + connectionProxyXA.init(); + return connectionProxyXA; + + } + + @Override + protected Connection getConnectionProxyXA() throws SQLException { + return getConnection(); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ExecuteTemplateXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ExecuteTemplateXA.java new file mode 100644 index 0000000..8936347 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ExecuteTemplateXA.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import io.seata.rm.datasource.exec.StatementCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.sql.Statement; + +/** + * The type Execute template. + * + * @author sharajava + */ +public class ExecuteTemplateXA { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExecuteTemplateXA.class); + + public static T execute(AbstractConnectionProxyXA connectionProxyXA, + StatementCallback statementCallback, + S targetStatement, + Object... args) throws SQLException { + boolean autoCommitStatus = connectionProxyXA.getAutoCommit(); + if (autoCommitStatus) { + // XA Start + connectionProxyXA.setAutoCommit(false); + } + try { + T res = null; + try { + // execute SQL + res = statementCallback.execute(targetStatement, args); + + } catch (Throwable ex) { + if (autoCommitStatus) { + // XA End & Rollback + try { + connectionProxyXA.rollback(); + } catch (SQLException sqle) { + // log and ignore the rollback failure. + LOGGER.warn( + "Failed to rollback xa branch of " + connectionProxyXA.xid + + "(caused by SQL execution failure(" + ex.getMessage() + ") since " + sqle.getMessage(), + sqle); + } + } + + if (ex instanceof SQLException) { + throw ex; + } else { + throw new SQLException(ex); + } + + } + if (autoCommitStatus) { + try { + // XA End & Prepare + connectionProxyXA.commit(); + } catch (Throwable ex) { + LOGGER.warn( + "Failed to commit xa branch of " + connectionProxyXA.xid + ") since " + ex.getMessage(), + ex); + // XA End & Rollback + if (!(ex instanceof SQLException) || !AbstractConnectionProxyXA.SQLSTATE_XA_NOT_END.equalsIgnoreCase(((SQLException) ex).getSQLState())) { + try { + connectionProxyXA.rollback(); + } catch (SQLException sqle) { + // log and ignore the rollback failure. + LOGGER.warn( + "Failed to rollback xa branch of " + connectionProxyXA.xid + + "(caused by commit failure(" + ex.getMessage() + ") since " + sqle.getMessage(), + sqle); + } + } + + if (ex instanceof SQLException) { + throw ex; + } else { + throw new SQLException(ex); + } + + } + } + return res; + } finally { + if (autoCommitStatus) { + connectionProxyXA.setAutoCommit(true); + } + + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holdable.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holdable.java new file mode 100644 index 0000000..2f0e527 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holdable.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +public interface Holdable { + + void setHeld(boolean held); + + boolean isHeld(); + + boolean shouldBeHeld(); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holder.java new file mode 100644 index 0000000..2d84217 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/Holder.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +public interface Holder { + + T hold(String key, T value); + + T release(String key, T value); + + T lookup(String key); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/PreparedStatementProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/PreparedStatementProxyXA.java new file mode 100644 index 0000000..d1f19e8 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/PreparedStatementProxyXA.java @@ -0,0 +1,357 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; + +/** + * PreparedStatement proxy for XA mode. + * + * @author sharajava + */ +public class PreparedStatementProxyXA extends StatementProxyXA implements PreparedStatement { + + public PreparedStatementProxyXA(AbstractConnectionProxyXA connectionProxyXA, PreparedStatement targetStatement) { + super(connectionProxyXA, targetStatement); + } + + private PreparedStatement getTargetStatement() { + return (PreparedStatement)targetStatement; + } + + @Override + public ResultSet executeQuery() throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeQuery(), getTargetStatement()); + } + + @Override + public int executeUpdate() throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate(), getTargetStatement()); + } + + @Override + public boolean execute() throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.execute(), getTargetStatement()); + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + getTargetStatement().setNull(parameterIndex, sqlType); + + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + getTargetStatement().setBoolean(parameterIndex, x); + + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + getTargetStatement().setByte(parameterIndex, x); + + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + getTargetStatement().setShort(parameterIndex, x); + + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + getTargetStatement().setInt(parameterIndex, x); + + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + getTargetStatement().setLong(parameterIndex, x); + + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + getTargetStatement().setFloat(parameterIndex, x); + + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + getTargetStatement().setDouble(parameterIndex, x); + + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + getTargetStatement().setBigDecimal(parameterIndex, x); + + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + getTargetStatement().setString(parameterIndex, x); + + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + getTargetStatement().setBytes(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + getTargetStatement().setDate(parameterIndex, x); + + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + getTargetStatement().setTime(parameterIndex, x); + + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + getTargetStatement().setTimestamp(parameterIndex, x); + + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + getTargetStatement().setAsciiStream(parameterIndex, x); + + } + + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + getTargetStatement().setUnicodeStream(parameterIndex, x, length); + + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + getTargetStatement().setBinaryStream(parameterIndex, x, length); + + } + + @Override + public void clearParameters() throws SQLException { + getTargetStatement().clearParameters(); + + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + getTargetStatement().setObject(parameterIndex, x, targetSqlType); + + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + getTargetStatement().setObject(parameterIndex, x); + + } + + @Override + public void addBatch() throws SQLException { + getTargetStatement().addBatch(); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + getTargetStatement().setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + getTargetStatement().setRef(parameterIndex, x); + + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + getTargetStatement().setBlob(parameterIndex, x); + + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + getTargetStatement().setClob(parameterIndex, x); + + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + getTargetStatement().setArray(parameterIndex, x); + + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return getTargetStatement().getMetaData(); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + getTargetStatement().setDate(parameterIndex, x, cal); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + getTargetStatement().setTime(parameterIndex, x, cal); + + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + getTargetStatement().setTimestamp(parameterIndex, x, cal); + + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + getTargetStatement().setNull(parameterIndex, sqlType, typeName); + + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + getTargetStatement().setURL(parameterIndex, x); + + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return getTargetStatement().getParameterMetaData(); + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + getTargetStatement().setRowId(parameterIndex, x); + + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + getTargetStatement().setNString(parameterIndex, value); + + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + getTargetStatement().setNCharacterStream(parameterIndex, value, length); + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + getTargetStatement().setNClob(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + getTargetStatement().setClob(parameterIndex, reader, length); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + getTargetStatement().setBlob(parameterIndex, inputStream, length); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + getTargetStatement().setNClob(parameterIndex, reader, length); + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + getTargetStatement().setSQLXML(parameterIndex, xmlObject); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { + getTargetStatement().setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + getTargetStatement().setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + getTargetStatement().setBinaryStream(parameterIndex, x, length); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + getTargetStatement().setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + getTargetStatement().setAsciiStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + getTargetStatement().setBinaryStream(parameterIndex, x); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + getTargetStatement().setCharacterStream(parameterIndex, reader); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + getTargetStatement().setNCharacterStream(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + getTargetStatement().setClob(parameterIndex, reader); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + getTargetStatement().setBlob(parameterIndex, inputStream); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + getTargetStatement().setNClob(parameterIndex, reader); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ResourceManagerXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ResourceManagerXA.java new file mode 100644 index 0000000..c1a1c92 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/ResourceManagerXA.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.rm.datasource.AbstractDataSourceCacheResourceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.transaction.xa.XAException; +import java.sql.SQLException; + +/** + * RM for XA mode. + * + * @author sharajava + */ +public class ResourceManagerXA extends AbstractDataSourceCacheResourceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceManagerXA.class); + + @Override + public void init() { + LOGGER.info("ResourceManagerXA init ..."); + + } + + @Override + public BranchType getBranchType() { + return BranchType.XA; + } + + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + return finishBranch(true, branchType, xid, branchId, resourceId, applicationData); + } + + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + return finishBranch(false, branchType, xid, branchId, resourceId, applicationData); + } + + private BranchStatus finishBranch(boolean committed, BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + XAXid xaBranchXid = XAXidBuilder.build(xid, branchId); + Resource resource = dataSourceCache.get(resourceId); + if (resource instanceof AbstractDataSourceProxyXA) { + try (ConnectionProxyXA connectionProxyXA = ((AbstractDataSourceProxyXA)resource).getConnectionForXAFinish(xaBranchXid)) { + if (committed) { + connectionProxyXA.xaCommit(xid, branchId, applicationData); + LOGGER.info(xaBranchXid + " was committed."); + return BranchStatus.PhaseTwo_Committed; + } else { + connectionProxyXA.xaRollback(xid, branchId, applicationData); + LOGGER.info(xaBranchXid + " was rollbacked"); + return BranchStatus.PhaseTwo_Rollbacked; + } + } catch (XAException | SQLException sqle) { + if (sqle instanceof XAException) { + if (((XAException)sqle).errorCode == XAException.XAER_NOTA) { + if (committed) { + return BranchStatus.PhaseTwo_Committed; + } else { + return BranchStatus.PhaseTwo_Rollbacked; + } + } + } + if (committed) { + LOGGER.info(xaBranchXid + " commit failed since " + sqle.getMessage(), sqle); + // FIXME: case of PhaseTwo_CommitFailed_Unretryable + return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } else { + LOGGER.info(xaBranchXid + " rollback failed since " + sqle.getMessage(), sqle); + // FIXME: case of PhaseTwo_RollbackFailed_Unretryable + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } + } + } else { + LOGGER.error("Unknown Resource for XA resource " + resourceId + " " + resource); + if (committed) { + return BranchStatus.PhaseTwo_CommitFailed_Unretryable; + } else { + return BranchStatus.PhaseTwo_RollbackFailed_Unretryable; + } + } + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/StatementProxyXA.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/StatementProxyXA.java new file mode 100644 index 0000000..e1798ab --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/StatementProxyXA.java @@ -0,0 +1,260 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +/** + * Statement proxy for XA mode. + * + * @author sharajava + */ +public class StatementProxyXA implements Statement { + + protected AbstractConnectionProxyXA connectionProxyXA; + + protected Statement targetStatement; + + public StatementProxyXA(AbstractConnectionProxyXA connectionProxyXA, Statement targetStatement) { + this.connectionProxyXA = connectionProxyXA; + this.targetStatement = targetStatement; + } + + @Override + public int executeUpdate(String sql) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate( + (String)args[0]), targetStatement, sql); + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate((String)args[0], (int)args[1]), targetStatement, sql, autoGeneratedKeys); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate((String)args[0], (int[])args[1]), targetStatement, sql, columnIndexes); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeUpdate((String)args[0], (String[])args[1]), targetStatement, sql, columnNames); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.execute((String)args[0], (int)args[1]), targetStatement, sql, autoGeneratedKeys); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.execute((String)args[0], (int[])args[1]), targetStatement, sql, columnIndexes); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.execute((String)args[0], (String[])args[1]), targetStatement, sql, columnNames); + } + + @Override + public boolean execute(String sql) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.execute((String)args[0]), targetStatement, sql); + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeQuery((String)args[0]), targetStatement, sql); + } + + @Override + public int[] executeBatch() throws SQLException { + return ExecuteTemplateXA.execute(connectionProxyXA, (statement, args) -> statement.executeBatch(), targetStatement); + } + + @Override + public void close() throws SQLException { + targetStatement.close(); + } + + @Override + public int getMaxFieldSize() throws SQLException { + return targetStatement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + targetStatement.setMaxFieldSize(max); + } + + @Override + public int getMaxRows() throws SQLException { + return targetStatement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + targetStatement.setMaxFieldSize(max); + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + targetStatement.setEscapeProcessing(enable); + } + + @Override + public int getQueryTimeout() throws SQLException { + return targetStatement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + targetStatement.setQueryTimeout(seconds); + } + + @Override + public void cancel() throws SQLException { + targetStatement.cancel(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return targetStatement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + targetStatement.clearWarnings(); + } + + @Override + public void setCursorName(String name) throws SQLException { + targetStatement.setCursorName(name); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return targetStatement.getResultSet(); + } + + @Override + public int getUpdateCount() throws SQLException { + return targetStatement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return targetStatement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + targetStatement.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return targetStatement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + targetStatement.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return targetStatement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return targetStatement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return targetStatement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + targetStatement.addBatch(sql); + } + + @Override + public void clearBatch() throws SQLException { + targetStatement.clearBatch(); + } + + @Override + public Connection getConnection() throws SQLException { + return targetStatement.getConnection(); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return targetStatement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return targetStatement.getGeneratedKeys(); + } + + @Override + public int getResultSetHoldability() throws SQLException { + return targetStatement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return targetStatement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + targetStatement.setPoolable(poolable); + } + + @Override + public boolean isPoolable() throws SQLException { + return targetStatement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + targetStatement.closeOnCompletion(); + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return targetStatement.isCloseOnCompletion(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return targetStatement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return targetStatement.isWrapperFor(iface); + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XABranchXid.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XABranchXid.java new file mode 100644 index 0000000..6a724c9 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XABranchXid.java @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import java.io.UnsupportedEncodingException; + +/** + * Xid in XA Protocol. Wrap info of Seata xid and branchId. + * + * @author sharajava + */ +public class XABranchXid implements XAXid { + + private static final String DEFAULT_ENCODE_CHARSET = "UTF-8"; + private static final String BRANCH_ID_PREFIX = "-"; + + private static final int SEATA_XA_XID_FORMAT_ID = 9752; + + private String xid; + + private long branchId; + + private byte[] globalTransactionId; + + private byte[] branchQualifier; + + XABranchXid(String xid, long branchId) { + this.xid = xid; + this.branchId = branchId; + encode(); + } + + XABranchXid(byte[] globalTransactionId, byte[] branchQualifier) { + this.globalTransactionId = globalTransactionId; + this.branchQualifier = branchQualifier; + decode(); + } + + @Override + public String getGlobalXid() { + return xid; + } + + @Override + public long getBranchId() { + return branchId; + } + + @Override + public int getFormatId() { + return SEATA_XA_XID_FORMAT_ID; + } + + @Override + public byte[] getGlobalTransactionId() { + return globalTransactionId; + } + + @Override + public byte[] getBranchQualifier() { + return branchQualifier; + } + + private byte[] string2byteArray(String string) { + try { + return string.getBytes(DEFAULT_ENCODE_CHARSET); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private String byteArray2String(byte[] bytes) { + try { + return new String(bytes, DEFAULT_ENCODE_CHARSET); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private void encode() { + if (xid == null) { + globalTransactionId = new byte[0]; + } else { + globalTransactionId = string2byteArray(xid); + } + + if (branchId == 0L) { + branchQualifier = new byte[0]; + } else { + branchQualifier = string2byteArray("-" + branchId); + } + } + + private void decode() { + if (globalTransactionId == null || globalTransactionId.length == 0) { + xid = null; + } else { + xid = byteArray2String(globalTransactionId); + } + + + if (branchQualifier == null || branchQualifier.length == 0) { + branchId = 0L; + } else { + String bs = byteArray2String(branchQualifier).substring(BRANCH_ID_PREFIX.length()); + branchId = Long.parseLong(bs); + } + + } + + @Override + public String toString() { + return xid + BRANCH_ID_PREFIX + branchId; + } +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXid.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXid.java new file mode 100644 index 0000000..43a43ca --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXid.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +import javax.transaction.xa.Xid; + +/** + * Seata XA Mode defined XA-Xid. + * + * @author sharajava + */ +public interface XAXid extends Xid { + + String getGlobalXid(); + + long getBranchId(); +} diff --git a/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXidBuilder.java b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXidBuilder.java new file mode 100644 index 0000000..56edbd4 --- /dev/null +++ b/rm-datasource/src/main/java/io/seata/rm/datasource/xa/XAXidBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.xa; + +/** + * XA-Xid builder. + * + * @author sharajava + */ +public class XAXidBuilder { + + private XAXidBuilder() { + } + + public static final XAXid build(String xid, long branchId) { + return new XABranchXid(xid, branchId); + } + + public static final XAXid build(byte[] globalTransactionId, byte[] branchQualifier) { + return new XABranchXid(globalTransactionId, branchQualifier); + } +} diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager b/rm-datasource/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager new file mode 100644 index 0000000..d10cb1e --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager @@ -0,0 +1,2 @@ +io.seata.rm.datasource.DataSourceManager +io.seata.rm.datasource.xa.ResourceManagerXA \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler new file mode 100644 index 0000000..14f8b2d --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler @@ -0,0 +1,2 @@ +io.seata.rm.RMHandlerAT +io.seata.rm.RMHandlerXA \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.exec.InsertExecutor b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.exec.InsertExecutor new file mode 100644 index 0000000..2169a39 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.exec.InsertExecutor @@ -0,0 +1,19 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.rm.datasource.exec.mysql.MySQLInsertExecutor +io.seata.rm.datasource.exec.oracle.OracleInsertExecutor +io.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.sql.struct.TableMetaCache b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.sql.struct.TableMetaCache new file mode 100644 index 0000000..f3838d8 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.sql.struct.TableMetaCache @@ -0,0 +1,3 @@ +io.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache +io.seata.rm.datasource.sql.struct.cache.OracleTableMetaCache +io.seata.rm.datasource.sql.struct.cache.PostgresqlTableMetaCache \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker new file mode 100644 index 0000000..6d674d9 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker @@ -0,0 +1,3 @@ +io.seata.rm.datasource.undo.oracle.keyword.OracleKeywordChecker +io.seata.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker +io.seata.rm.datasource.undo.postgresql.keyword.PostgresqlKeywordChecker \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoExecutorHolder b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoExecutorHolder new file mode 100644 index 0000000..ddc7a35 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoExecutorHolder @@ -0,0 +1,3 @@ +io.seata.rm.datasource.undo.mysql.MySQLUndoExecutorHolder +io.seata.rm.datasource.undo.oracle.OracleUndoExecutorHolder +io.seata.rm.datasource.undo.postgresql.PostgresqlUndoExecutorHolder \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogManager b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogManager new file mode 100644 index 0000000..a9de446 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogManager @@ -0,0 +1,3 @@ +io.seata.rm.datasource.undo.mysql.MySQLUndoLogManager +io.seata.rm.datasource.undo.oracle.OracleUndoLogManager +io.seata.rm.datasource.undo.postgresql.PostgresqlUndoLogManager \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogParser b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogParser new file mode 100644 index 0000000..f9f9190 --- /dev/null +++ b/rm-datasource/src/main/resources/META-INF/services/io.seata.rm.datasource.undo.UndoLogParser @@ -0,0 +1,5 @@ +io.seata.rm.datasource.undo.parser.FastjsonUndoLogParser +io.seata.rm.datasource.undo.parser.JacksonUndoLogParser +io.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser +io.seata.rm.datasource.undo.parser.KryoUndoLogParser +io.seata.rm.datasource.undo.parser.FstUndoLogParser \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/GlobalLockTemplateTest.java b/rm-datasource/src/test/java/io/seata/rm/GlobalLockTemplateTest.java new file mode 100644 index 0000000..39ca424 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/GlobalLockTemplateTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.context.GlobalLockConfigHolder; +import io.seata.core.context.RootContext; +import io.seata.core.model.GlobalLockConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author selfishlover + */ +public class GlobalLockTemplateTest { + + private final GlobalLockTemplate template = new GlobalLockTemplate(); + + private final GlobalLockConfig config1 = generateGlobalLockConfig(); + + private final GlobalLockConfig config2 = generateGlobalLockConfig(); + + @BeforeEach + void setUp() { + RootContext.unbindGlobalLockFlag(); + GlobalLockConfigHolder.remove(); + } + + @Test + void testSingle() { + assertDoesNotThrow(() -> { + template.execute(new GlobalLockExecutor() { + @Override + public Object execute() { + assertTrue(RootContext.requireGlobalLock(), "fail to bind global lock flag"); + assertSame(config1, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), + "global lock config changed during execution"); + return null; + } + + @Override + public GlobalLockConfig getGlobalLockConfig() { + return config1; + } + }); + }); + } + + @Test + void testNested() { + assertDoesNotThrow(() -> { + template.execute(new GlobalLockExecutor() { + @Override + public Object execute() { + assertTrue(RootContext.requireGlobalLock(), "fail to bind global lock flag"); + assertSame(config1, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), + "global lock config changed during execution"); + assertDoesNotThrow(() -> { + template.execute(new GlobalLockExecutor() { + @Override + public Object execute() { + assertTrue(RootContext.requireGlobalLock(), "inner lost global lock flag"); + assertSame(config2, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), + "fail to set inner global lock config"); + return null; + } + + @Override + public GlobalLockConfig getGlobalLockConfig() { + return config2; + } + }); + }); + assertTrue(RootContext.requireGlobalLock(), "outer lost global lock flag"); + assertSame(config1, GlobalLockConfigHolder.getCurrentGlobalLockConfig(), + "outer global lock config was not restored"); + return null; + } + + @Override + public GlobalLockConfig getGlobalLockConfig() { + return config1; + } + }); + }); + } + + @AfterEach + void tearDown() { + assertFalse(RootContext.requireGlobalLock(), "fail to unbind global lock flag"); + assertNull(GlobalLockConfigHolder.getCurrentGlobalLockConfig(), "fail to clean global lock config"); + } + + private GlobalLockConfig generateGlobalLockConfig() { + GlobalLockConfig config = new GlobalLockConfig(); + config.setLockRetryInternal(100); + config.setLockRetryTimes(3); + return config; + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/AsyncWorkerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/AsyncWorkerTest.java new file mode 100644 index 0000000..a7b831d --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/AsyncWorkerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.core.model.BranchStatus; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AsyncWorkerTest { + + private final AsyncWorker worker = new AsyncWorker(null); + + private final Random random = new Random(); + + @Test + void branchCommit() { + BranchStatus status = worker.branchCommit("test", 0, null); + assertEquals(BranchStatus.PhaseTwo_Committed, status, "should return PhaseTwo_Committed"); + } + + @Test + void doBranchCommitSafely() { + assertDoesNotThrow(worker::doBranchCommitSafely, "this method should never throw anything"); + } + + @Test + void groupedByResourceId() { + List contexts = getRandomContexts(); + Map> groupedContexts = worker.groupedByResourceId(contexts); + groupedContexts.forEach((resourceId, group) -> group.forEach(context -> { + String message = "each context in the group should has the same resourceId"; + assertEquals(resourceId, context.resourceId, message); + })); + } + + private List getRandomContexts() { + return random.ints().limit(16) + .mapToObj(String::valueOf) + .flatMap(this::generateContextStream) + .collect(Collectors.toList()); + } + + private Stream generateContextStream(String resourceId) { + int size = random.nextInt(10); + return IntStream.range(0, size).mapToObj(i -> buildContext(resourceId)); + } + + private AsyncWorker.Phase2Context buildContext(String resourceId) { + return new AsyncWorker.Phase2Context("test", 0, resourceId); + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/ColumnUtilsTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/ColumnUtilsTest.java new file mode 100644 index 0000000..864c557 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/ColumnUtilsTest.java @@ -0,0 +1,231 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jsbxyyx + */ +public class ColumnUtilsTest { + + @Test + public void test_delEscape_byEscape() throws Exception { + List cols = new ArrayList<>(); + cols.add("`id`"); + cols.add("name"); + cols = ColumnUtils.delEscape(cols, ColumnUtils.Escape.MYSQL); + Assertions.assertEquals("id", cols.get(0)); + Assertions.assertEquals("name", cols.get(1)); + + List cols2 = new ArrayList<>(); + cols2.add("\"id\""); + cols2 = ColumnUtils.delEscape(cols2, ColumnUtils.Escape.STANDARD); + Assertions.assertEquals("id", cols2.get(0)); + + List cols3 = new ArrayList<>(); + cols3.add("\"scheme\".\"id\""); + cols3 = ColumnUtils.delEscape(cols3, ColumnUtils.Escape.STANDARD); + Assertions.assertEquals("scheme.id", cols3.get(0)); + + List cols4 = new ArrayList<>(); + cols4.add("`scheme`.`id`"); + cols4 = ColumnUtils.delEscape(cols4, ColumnUtils.Escape.MYSQL); + Assertions.assertEquals("scheme.id", cols4.get(0)); + + List cols5 = new ArrayList<>(); + cols5.add("\"scheme\".id"); + cols5 = ColumnUtils.delEscape(cols5, ColumnUtils.Escape.STANDARD); + Assertions.assertEquals("scheme.id", cols5.get(0)); + + List cols6 = new ArrayList<>(); + cols6.add("\"tab\"\"le\""); + cols6 = ColumnUtils.delEscape(cols6, ColumnUtils.Escape.STANDARD); + Assertions.assertEquals("tab\"\"le", cols6.get(0)); + + List cols7 = new ArrayList<>(); + cols7.add("scheme.\"id\""); + cols7 = ColumnUtils.delEscape(cols7, ColumnUtils.Escape.STANDARD); + Assertions.assertEquals("scheme.id", cols7.get(0)); + + List cols8 = new ArrayList<>(); + cols8.add("`scheme`.id"); + cols8 = ColumnUtils.delEscape(cols8, ColumnUtils.Escape.MYSQL); + Assertions.assertEquals("scheme.id", cols8.get(0)); + + List cols9 = new ArrayList<>(); + cols9.add("scheme.`id`"); + cols9 = ColumnUtils.delEscape(cols9, ColumnUtils.Escape.MYSQL); + Assertions.assertEquals("scheme.id", cols9.get(0)); + + Assertions.assertNull(ColumnUtils.delEscape((String) null, ColumnUtils.Escape.MYSQL)); + } + + @Test + public void test_delEscape_byDbType() throws Exception { + + List cols3 = new ArrayList<>(); + cols3.add("\"id\""); + cols3 = ColumnUtils.delEscape(cols3, JdbcConstants.ORACLE); + Assertions.assertEquals("id", cols3.get(0)); + + List cols4 = new ArrayList<>(); + cols4.add("`id`"); + cols4 = ColumnUtils.delEscape(cols4, JdbcConstants.MYSQL); + Assertions.assertEquals("id", cols4.get(0)); + + List cols5 = new ArrayList<>(); + cols5.add("\"id\""); + cols5 = ColumnUtils.delEscape(cols5, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("id", cols5.get(0)); + + Assertions.assertEquals("id", ColumnUtils.delEscape("`id`", JdbcConstants.MYSQL)); + Assertions.assertEquals("id", ColumnUtils.delEscape("\"id\"", JdbcConstants.ORACLE)); + Assertions.assertEquals("id", ColumnUtils.delEscape("\"id\"", JdbcConstants.POSTGRESQL)); + } + + @Test + public void test_addEscape_byDbType() throws Exception { + List cols = new ArrayList<>(); + cols.add("id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("id", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("`id`"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("`id`", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("from"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("`from`", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("scheme.id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("scheme.id", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("`scheme`.id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("`scheme`.id", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("scheme.`id`"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.MYSQL); + Assertions.assertEquals("scheme.`id`", cols.get(0)); + + + cols = new ArrayList<>(); + cols.add("id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"id\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("\"id\""); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"id\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("from"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"from\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("FROM"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"FROM\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("ID"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("ID", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("\"SCHEME\".ID"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"SCHEME\".ID", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("\"scheme\".id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"scheme\".\"id\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("SCHEME.\"ID\""); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("SCHEME.\"ID\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("scheme.id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.ORACLE); + Assertions.assertEquals("\"scheme\".\"id\"", cols.get(0)); + + + cols = new ArrayList<>(); + cols.add("id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("id", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("Id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"Id\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("from"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"from\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("FROM"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"FROM\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("scheme.Id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"scheme\".\"Id\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("SCHEME.\"ID\""); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"SCHEME\".\"ID\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("\"SCHEME\".ID"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"SCHEME\".\"ID\"", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("scheme.id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("scheme.id", cols.get(0)); + + cols = new ArrayList<>(); + cols.add("schEme.id"); + cols = ColumnUtils.addEscape(cols, JdbcConstants.POSTGRESQL); + Assertions.assertEquals("\"schEme\".\"id\"", cols.get(0)); + + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionContextProxyTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionContextProxyTest.java new file mode 100644 index 0000000..50ef8ea --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionContextProxyTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import com.alibaba.druid.mock.MockSavepoint; +import io.seata.rm.datasource.undo.SQLUndoLog; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Savepoint; +import java.util.List; + +/** + * ConnectionContextProxy test + * + * @author chd + */ +public class ConnectionContextProxyTest { + ConnectionContext connectionContext = new ConnectionContext(); + + @Test + public void testBuildLockKeys() throws Exception { + connectionContext.appendLockKey("abc"); + connectionContext.appendLockKey("bcd"); + + Assertions.assertTrue(connectionContext.hasLockKey()); + Assertions.assertEquals(connectionContext.buildLockKeys(), "bcd;abc"); + } + + @Test + public void testAppendUndoItem() { + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + connectionContext.appendUndoItem(sqlUndoLog); + SQLUndoLog sqlUndoLog1 = new SQLUndoLog(); + connectionContext.appendUndoItem(sqlUndoLog1); + + Assertions.assertTrue(connectionContext.hasUndoLog()); + Assertions.assertEquals(connectionContext.getUndoItems().size(), 2); + Assertions.assertSame(connectionContext.getUndoItems().get(0), sqlUndoLog); + Assertions.assertSame(connectionContext.getUndoItems().get(1), sqlUndoLog1); + } + + @Test + @SuppressWarnings("unchecked") + public void testGetAfterSavepoints() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Savepoint sp1 = new MockSavepoint(); + Savepoint sp2 = new MockSavepoint(); + Savepoint sp3 = new MockSavepoint(); + connectionContext.appendSavepoint(sp1); + connectionContext.appendSavepoint(sp2); + connectionContext.appendSavepoint(sp3); + + Method m = ConnectionContext.class.getDeclaredMethod("getAfterSavepoints", Savepoint.class); + m.setAccessible(true); + + List invoke = (List) m.invoke(connectionContext, new Object[]{null}); + Assertions.assertEquals(invoke.size(), 3); + + invoke = (List) m.invoke(connectionContext, sp2); + Assertions.assertEquals(invoke.size(), 2); + } + + @Test + public void testBindAndUnbind() { + connectionContext.bind("test-xid"); + Assertions.assertTrue(connectionContext.inGlobalTransaction()); + + connectionContext.reset(); + + connectionContext.setGlobalLockRequire(true); + Assertions.assertTrue(connectionContext.isGlobalLockRequire()); + } + + @Test + public void testRemoveSavepoint() { + Savepoint sp1 = new MockSavepoint(); + connectionContext.appendSavepoint(sp1); + connectionContext.appendUndoItem(new SQLUndoLog()); + connectionContext.appendLockKey("sp1-lock-key"); + + Savepoint sp2 = new MockSavepoint(); + connectionContext.appendSavepoint(sp2); + + Savepoint sp3 = new MockSavepoint(); + connectionContext.appendSavepoint(sp3); + connectionContext.appendLockKey("sp3-lock-key"); + connectionContext.appendUndoItem(new SQLUndoLog()); + + Assertions.assertEquals(connectionContext.getUndoItems().size(), 2); + Assertions.assertEquals(connectionContext.buildLockKeys(), "sp3-lock-key;sp1-lock-key"); + + + connectionContext.removeSavepoint(sp3); + Assertions.assertEquals(connectionContext.getUndoItems().size(), 1); + Assertions.assertEquals(connectionContext.buildLockKeys(), "sp1-lock-key"); + + connectionContext.removeSavepoint(null); + Assertions.assertEquals(connectionContext.getUndoItems().size(), 0); + Assertions.assertNull(connectionContext.buildLockKeys()); + } + + + @Test + public void testReleaseSavepoint() { + Savepoint sp1 = new MockSavepoint(); + connectionContext.appendSavepoint(sp1); + connectionContext.appendUndoItem(new SQLUndoLog()); + connectionContext.appendLockKey("sp1-lock-key"); + + Savepoint sp2 = new MockSavepoint(); + connectionContext.appendSavepoint(sp2); + + Savepoint sp3 = new MockSavepoint(); + connectionContext.appendSavepoint(sp3); + connectionContext.appendLockKey("sp3-lock-key"); + connectionContext.appendUndoItem(new SQLUndoLog()); + + Assertions.assertEquals(connectionContext.getUndoItems().size(), 2); + Assertions.assertEquals(connectionContext.buildLockKeys(), "sp3-lock-key;sp1-lock-key"); + + + connectionContext.releaseSavepoint(sp3); + Assertions.assertEquals(connectionContext.getUndoItems().size(), 2); + Assertions.assertEquals(connectionContext.buildLockKeys(), "sp3-lock-key;sp1-lock-key"); + + connectionContext.releaseSavepoint(null); + Assertions.assertEquals(connectionContext.getUndoItems().size(), 2); + Assertions.assertEquals(connectionContext.buildLockKeys(), "sp3-lock-key;sp1-lock-key"); + } + + + + @AfterEach + public void clear() { + connectionContext.reset(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionProxyTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionProxyTest.java new file mode 100644 index 0000000..7170f8c --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/ConnectionProxyTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.datasource.exec.LockConflictException; +import io.seata.rm.datasource.exec.LockWaitTimeoutException; +import io.seata.rm.datasource.undo.SQLUndoLog; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * ConnectionProxy test + * + * @author ggndnn + */ +public class ConnectionProxyTest { + private DataSourceProxy dataSourceProxy; + + private final static String TEST_RESOURCE_ID = "testResourceId"; + + private final static String TEST_XID = "testXid"; + + private final static String lockKey = "order:123"; + + private Field branchRollbackFlagField; + + @BeforeEach + public void initBeforeEach() throws Exception { + branchRollbackFlagField = ConnectionProxy.LockRetryPolicy.class.getDeclaredField("LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(branchRollbackFlagField, branchRollbackFlagField.getModifiers() & ~Modifier.FINAL); + branchRollbackFlagField.setAccessible(true); + boolean branchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + Assertions.assertTrue(branchRollbackFlag); + + dataSourceProxy = Mockito.mock(DataSourceProxy.class); + Mockito.when(dataSourceProxy.getResourceId()) + .thenReturn(TEST_RESOURCE_ID); + ResourceManager rm = Mockito.mock(ResourceManager.class); + Mockito.when(rm.branchRegister(BranchType.AT, dataSourceProxy.getResourceId(), null, TEST_XID, null, lockKey)) + .thenThrow(new TransactionException(TransactionExceptionCode.LockKeyConflict)); + DefaultResourceManager defaultResourceManager = DefaultResourceManager.get(); + Assertions.assertNotNull(defaultResourceManager); + DefaultResourceManager.mockResourceManager(BranchType.AT, rm); + } + + @Test + public void testLockRetryPolicyRollbackOnConflict() throws Exception { + boolean oldBranchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + branchRollbackFlagField.set(null, true); + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, null); + connectionProxy.bind(TEST_XID); + connectionProxy.appendUndoLog(new SQLUndoLog()); + connectionProxy.appendLockKey(lockKey); + Assertions.assertThrows(LockConflictException.class, connectionProxy::commit); + branchRollbackFlagField.set(null, oldBranchRollbackFlag); + } + + @Test + public void testLockRetryPolicyNotRollbackOnConflict() throws Exception { + boolean oldBranchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + branchRollbackFlagField.set(null, false); + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, null); + connectionProxy.bind(TEST_XID); + connectionProxy.appendUndoLog(new SQLUndoLog()); + connectionProxy.appendLockKey(lockKey); + Assertions.assertThrows(LockWaitTimeoutException.class, connectionProxy::commit); + branchRollbackFlagField.set(null, oldBranchRollbackFlag); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/DataCompareUtilsTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/DataCompareUtilsTest.java new file mode 100644 index 0000000..52ced27 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/DataCompareUtilsTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.JDBCType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * @author Geng Zhang + */ +public class DataCompareUtilsTest { + + @Test + public void isFieldEquals() { + Field field0 = new Field("name", 0, "111"); + Field field1 = new Field("name", 1, "111"); + Field field2 = new Field("name", 0, "222"); + Field field3 = new Field("age", 0, "222"); + Field field4 = new Field("name", 0, null); + + Assertions.assertFalse(DataCompareUtils.isFieldEquals(field0, null).getResult()); + Assertions.assertFalse(DataCompareUtils.isFieldEquals(null, field0).getResult()); + Assertions.assertFalse(DataCompareUtils.isFieldEquals(field0, field1).getResult()); + Assertions.assertFalse(DataCompareUtils.isFieldEquals(field0, field2).getResult()); + Assertions.assertFalse(DataCompareUtils.isFieldEquals(field0, field3).getResult()); + Assertions.assertFalse(DataCompareUtils.isFieldEquals(field0, field4).getResult()); + + Field field10 = new Field("Name", 0, "111"); + Field field11 = new Field("Name", 0, null); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field0, field10).getResult()); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field4, field11).getResult()); + + Field field12 = new Field("information", JDBCType.BLOB.getVendorTypeNumber(), "hello world".getBytes()); + Field field13 = new Field("information", JDBCType.BLOB.getVendorTypeNumber(), "hello world".getBytes()); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field12, field13).getResult()); + } + + @Test + public void isRecordsEquals() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"pk"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("table_name"); + beforeImage.setTableMeta(tableMeta); + + List rows = new ArrayList<>(); + Row row = new Row(); + Field field01 = addField(row,"pk", 1, "12345"); + Field field02 = addField(row,"age", 1, "18"); + rows.add(row); + beforeImage.setRows(rows); + + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, null).getResult()); + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(null, beforeImage).getResult()); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("table_name1"); // wrong table name + afterImage.setTableMeta(tableMeta); + + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + afterImage.setTableName("table_name"); + + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + + List rows2 = new ArrayList<>(); + Row row2 = new Row(); + Field field11 = addField(row2,"pk", 1, "12345"); + Field field12 = addField(row2,"age", 1, "18"); + rows2.add(row2); + afterImage.setRows(rows2); + Assertions.assertTrue(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + + field11.setValue("23456"); + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + field11.setValue("12345"); + + field12.setName("sex"); + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + field12.setName("age"); + + field12.setValue("19"); + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + field12.setName("18"); + + Field field3 = new Field("pk", 1, "12346"); + Row row3 = new Row(); + row3.add(field3); + rows2.add(row3); + Assertions.assertFalse(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + + + beforeImage.setRows(new ArrayList<>()); + afterImage.setRows(new ArrayList<>()); + Assertions.assertTrue(DataCompareUtils.isRecordsEquals(beforeImage, afterImage).getResult()); + } + + private Field addField(Row row, String name, int type, Object value){ + Field field = new Field(name, type, value); + row.add(field); + return field; + } + + @Test + public void isRowsEquals() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"pk"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + List rows = new ArrayList<>(); + Field field = new Field("pk", 1, "12345"); + Row row = new Row(); + row.add(field); + rows.add(row); + + Assertions.assertFalse(DataCompareUtils.isRowsEquals(tableMeta, rows, null).getResult()); + Assertions.assertFalse(DataCompareUtils.isRowsEquals(tableMeta, null, rows).getResult()); + + List rows2 = new ArrayList<>(); + Field field2 = new Field("pk", 1, "12345"); + Row row2 = new Row(); + row2.add(field2); + rows2.add(row2); + Assertions.assertTrue(DataCompareUtils.isRowsEquals(tableMeta, rows, rows2).getResult()); + + field.setValue("23456"); + Assertions.assertFalse(DataCompareUtils.isRowsEquals(tableMeta, rows, rows2).getResult()); + field.setValue("12345"); + + Field field3 = new Field("pk", 1, "12346"); + Row row3 = new Row(); + row3.add(field3); + rows2.add(row3); + Assertions.assertFalse(DataCompareUtils.isRowsEquals(tableMeta, rows, rows2).getResult()); + } + + @Test + public void testRowListToMapWithSinglePk(){ + List primaryKeyList = new ArrayList<>(); + primaryKeyList.add("id"); + + List rows = new ArrayList<>(); + Field field = new Field("id", 1, "1"); + Row row = new Row(); + row.add(field); + rows.add(row); + + Field field2 = new Field("id", 1, "2"); + Row row2 = new Row(); + row2.add(field2); + rows.add(row2); + + Field field3 = new Field("id", 1, "3"); + Row row3 = new Row(); + row3.add(field3); + rows.add(row3); + + Map> result =DataCompareUtils.rowListToMap(rows,primaryKeyList); + Assertions.assertTrue(result.size()==3); + Assertions.assertEquals(result.keySet().iterator().next(),"1"); + + } + + + @Test + public void testRowListToMapWithMultipPk(){ + List primaryKeyList = new ArrayList<>(); + primaryKeyList.add("id1"); + primaryKeyList.add("id2"); + + List rows = new ArrayList<>(); + Field field1 = new Field("id1", 1, "1"); + Field field11 = new Field("id2", 1, "2"); + Row row = new Row(); + row.add(field1); + row.add(field11); + rows.add(row); + + Field field2 = new Field("id1", 1, "3"); + Field field22 = new Field("id2", 1, "4"); + Row row2 = new Row(); + row2.add(field2); + row2.add(field22); + rows.add(row2); + + Field field3 = new Field("id1", 1, "5"); + Field field33 = new Field("id2", 1, "6"); + Row row3 = new Row(); + row3.add(field3); + row3.add(field33); + rows.add(row3); + + Map> result =DataCompareUtils.rowListToMap(rows,primaryKeyList); + Assertions.assertTrue(result.size()==3); + Assertions.assertEquals(result.keySet().iterator().next(),"1_2"); + + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/DataSourceProxyTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/DataSourceProxyTest.java new file mode 100644 index 0000000..c75200c --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/DataSourceProxyTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import javax.sql.DataSource; +import java.lang.reflect.Field; +import java.sql.SQLException; + +import com.alibaba.druid.pool.DruidDataSource; +import io.seata.rm.datasource.mock.MockDataSource; +import io.seata.rm.datasource.mock.MockDriver; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author ph3636 + */ +public class DataSourceProxyTest { + + @Test + public void test_constructor() { + DataSource dataSource = new MockDataSource(); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + Assertions.assertEquals(dataSourceProxy.getTargetDataSource(), dataSource); + + DataSourceProxy dataSourceProxy2 = new DataSourceProxy(dataSourceProxy); + Assertions.assertEquals(dataSourceProxy2.getTargetDataSource(), dataSource); + } + + @Test + public void getResourceIdTest() throws SQLException, NoSuchFieldException, IllegalAccessException { + MockDriver mockDriver = new MockDriver(); + String username = "username"; + + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + dataSource.setUsername(username); + dataSource.setPassword("password"); + + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + Field dbTypeField = proxy.getClass().getDeclaredField("dbType"); + dbTypeField.setAccessible(true); + dbTypeField.set(proxy, io.seata.sqlparser.util.JdbcConstants.ORACLE); + + String userName = dataSource.getConnection().getMetaData().getUserName(); + Assertions.assertEquals(userName, username); + + Field userNameField = proxy.getClass().getDeclaredField("userName"); + userNameField.setAccessible(true); + userNameField.set(proxy, username); + + Assertions.assertEquals(proxy.getResourceId(), "jdbc:mock:xxx/username"); + + dbTypeField.set(proxy, io.seata.sqlparser.util.JdbcConstants.MYSQL); + Assertions.assertEquals(proxy.getResourceId(), "jdbc:mock:xxx"); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/PreparedStatementProxyTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/PreparedStatementProxyTest.java new file mode 100644 index 0000000..cd7c58c --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/PreparedStatementProxyTest.java @@ -0,0 +1,369 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.sql.ResultSet; +import java.util.Calendar; +import java.util.List; +import java.util.Objects; + +import com.alibaba.druid.mock.MockArray; +import com.alibaba.druid.mock.MockNClob; +import com.alibaba.druid.mock.MockRef; +import com.alibaba.druid.mock.MockSQLXML; +import com.alibaba.druid.pool.DruidDataSource; + +import com.google.common.collect.Lists; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.mock.MockBlob; +import io.seata.rm.datasource.mock.MockClob; +import io.seata.rm.datasource.mock.MockConnection; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.sqlparser.SQLRecognizerFactory; +import io.seata.sqlparser.SqlParserType; +import io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolderFactory; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PreparedStatementProxyTest { + + private static List returnValueColumnLabels = Lists.newArrayList("id", "name"); + + private static Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + + private static Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_prepared_statement_proxy", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_prepared_statement_proxy", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + + private static Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + private static PreparedStatementProxy preparedStatementProxy; + + private static TestUnusedConstructorPreparedStatementProxy unusedConstructorPreparedStatementProxy; + + @BeforeAll + public static void init() throws SQLException { + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + + String sql = "update prepared_statement_proxy set name = ?"; + + PreparedStatement preparedStatement = mockDriver.createSeataMockPreparedStatement( + (MockConnection)connectionProxy.getTargetConnection(), sql); + + preparedStatementProxy = new PreparedStatementProxy(connectionProxy, preparedStatement, sql); + unusedConstructorPreparedStatementProxy = new TestUnusedConstructorPreparedStatementProxy(connectionProxy, preparedStatement); + EnhancedServiceLoader.load(SQLOperateRecognizerHolder.class, JdbcConstants.MYSQL, + SQLOperateRecognizerHolderFactory.class.getClassLoader()); + DruidDelegatingSQLRecognizerFactory recognizerFactory = (DruidDelegatingSQLRecognizerFactory) EnhancedServiceLoader + .load(SQLRecognizerFactory.class, SqlParserType.SQL_PARSER_TYPE_DRUID); + } + + @Test + public void testPreparedStatementProxy() { + Assertions.assertNotNull(preparedStatementProxy); + Assertions.assertNotNull(unusedConstructorPreparedStatementProxy); + } + + @Test + public void testExecute() throws SQLException { + preparedStatementProxy.execute(); + } + + @Test + public void testExecuteUpdate() throws SQLException { + Assertions.assertNotNull(preparedStatementProxy.executeUpdate()); + } + + @Test + public void testExecuteQuery() throws SQLException { + Assertions.assertNotNull(preparedStatementProxy.executeQuery()); + } + + @Test + public void testGetSetParamsByIndex() { + preparedStatementProxy.setParamByIndex(1, "xxx"); + Assertions.assertEquals("xxx", preparedStatementProxy.getParamsByIndex(1).get(0)); + } + + @Test + public void testSetParam() throws SQLException, MalformedURLException { + preparedStatementProxy.clearParameters(); + preparedStatementProxy.setNull(1, JDBCType.DECIMAL.getVendorTypeNumber()); + Assertions.assertEquals(Null.get(), preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNull(1, JDBCType.DECIMAL.getVendorTypeNumber(), "NULL"); + Assertions.assertEquals(Null.get(), preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBoolean(1, true); + Assertions.assertEquals(true, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setByte(1, (byte)0); + Assertions.assertEquals((byte)0, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setShort(1, (short)0); + Assertions.assertEquals((short)0, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setInt(1, 0); + Assertions.assertEquals(0, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setLong(1, 0L); + Assertions.assertEquals(0L, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setFloat(1, 0f); + Assertions.assertEquals(0f, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setDouble(1, 1.1); + Assertions.assertEquals(1.1, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBigDecimal(1, new BigDecimal(0)); + Assertions.assertEquals(new BigDecimal(0), preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setString(1, "x"); + Assertions.assertEquals("x", preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNString(1, "x"); + Assertions.assertEquals("x", preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBytes(1, "x".getBytes()); + Assertions.assertTrue(Objects.deepEquals("x".getBytes(), preparedStatementProxy.getParamsByIndex(1).get(0))); + preparedStatementProxy.clearParameters(); + + Date date = new Date(System.currentTimeMillis()); + preparedStatementProxy.setDate(1, date); + Assertions.assertEquals(date, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setDate(1, date, Calendar.getInstance()); + Assertions.assertEquals(date, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + Time time = new Time(System.currentTimeMillis()); + preparedStatementProxy.setTime(1, time); + Assertions.assertEquals(time, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setTime(1, time, Calendar.getInstance()); + Assertions.assertEquals(time, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + preparedStatementProxy.setTimestamp(1, timestamp); + Assertions.assertEquals(timestamp, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setTimestamp(1, timestamp, Calendar.getInstance()); + Assertions.assertEquals(timestamp, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream("x".getBytes(), 0, 1); + preparedStatementProxy.setAsciiStream(1, byteArrayInputStream); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setAsciiStream(1, byteArrayInputStream, 1L); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setAsciiStream(1, byteArrayInputStream); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setUnicodeStream(1, byteArrayInputStream, 1); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBinaryStream(1, byteArrayInputStream); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBinaryStream(1, byteArrayInputStream, 1L); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBinaryStream(1, byteArrayInputStream, 1); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setObject(1, 1, JDBCType.INTEGER.getVendorTypeNumber()); + Assertions.assertEquals(1, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setObject(1, 1, JDBCType.INTEGER.getVendorTypeNumber(), 1); + Assertions.assertEquals(1, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setObject(1, 1); + Assertions.assertEquals(1, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + Assertions.assertDoesNotThrow(() -> preparedStatementProxy.addBatch()); + + CharArrayReader charArrayReader = new CharArrayReader("x".toCharArray()); + preparedStatementProxy.setCharacterStream(1, charArrayReader, 1); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setCharacterStream(1, charArrayReader, 1L); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setCharacterStream(1, charArrayReader); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNCharacterStream(1, charArrayReader); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNCharacterStream(1, charArrayReader, 1L); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockRef ref = new MockRef(); + preparedStatementProxy.setRef(1, ref); + Assertions.assertEquals(ref, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockBlob blob = new MockBlob(); + preparedStatementProxy.setBlob(1, blob); + Assertions.assertEquals(blob, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBlob(1, byteArrayInputStream); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setBlob(1, byteArrayInputStream, 1L); + Assertions.assertEquals(byteArrayInputStream, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockClob clob = new MockClob(); + preparedStatementProxy.setClob(1, clob); + Assertions.assertEquals(clob, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setClob(1, charArrayReader, 1L); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setClob(1, charArrayReader); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockNClob nclob = new MockNClob(); + preparedStatementProxy.setNClob(1, nclob); + Assertions.assertEquals(nclob, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNClob(1, charArrayReader, 1L); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + preparedStatementProxy.setNClob(1, charArrayReader); + Assertions.assertEquals(charArrayReader, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockArray array = new MockArray(); + preparedStatementProxy.setArray(1, array); + Assertions.assertEquals(array, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + Assertions.assertNotNull(preparedStatementProxy.getMetaData()); + Assertions.assertNotNull(preparedStatementProxy.getParameterMetaData()); + + URL url = new URL("http", "", 8080, ""); + preparedStatementProxy.setURL(1, url); + Assertions.assertEquals(url, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + MockSQLXML sqlxml = new MockSQLXML(); + preparedStatementProxy.setSQLXML(1, sqlxml); + Assertions.assertEquals(sqlxml, preparedStatementProxy.getParamsByIndex(1).get(0)); + preparedStatementProxy.clearParameters(); + + Assertions.assertNotNull(preparedStatementProxy.getParameters()); + } + + /** + * This class use for test the unused constructor in AbstractPreparedStatementProxy + */ + private static class TestUnusedConstructorPreparedStatementProxy extends AbstractPreparedStatementProxy { + + public TestUnusedConstructorPreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement) throws SQLException { + super(connectionProxy, targetStatement); + } + + @Override + public ResultSet executeQuery() throws SQLException { + return null; + } + + @Override + public int executeUpdate() throws SQLException { + return 0; + } + + @Override + public boolean execute() throws SQLException { + return false; + } + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/SqlGenerateUtilsTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/SqlGenerateUtilsTest.java new file mode 100644 index 0000000..0a56ec3 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/SqlGenerateUtilsTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author slievrly + */ +class SqlGenerateUtilsTest { + + + @Test + void testBuildWhereConditionByPKs() throws SQLException { + List pkNameList=new ArrayList<>(); + pkNameList.add("id"); + pkNameList.add("name"); + String result = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList,4,"mysql",2); + Assertions.assertEquals("(id,name) in ( (?,?),(?,?) ) or (id,name) in ( (?,?),(?,?) )", result); + result = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList,5,"mysql",2); + Assertions.assertEquals("(id,name) in ( (?,?),(?,?) ) or (id,name) in ( (?,?),(?,?) ) or (id,name) in ( (?,?)" + + " )", + result); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/StatementProxyTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/StatementProxyTest.java new file mode 100644 index 0000000..8260ef8 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/StatementProxyTest.java @@ -0,0 +1,295 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.sql.Types; +import java.util.List; + +import com.alibaba.druid.mock.MockResultSet; +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.jdbc.ResultSetMetaDataBase; + +import com.google.common.collect.Lists; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.mock.MockConnection; +import io.seata.rm.datasource.mock.MockDriver; + +import io.seata.sqlparser.SQLRecognizerFactory; +import io.seata.sqlparser.SqlParserType; +import io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolderFactory; +import io.seata.sqlparser.util.JdbcConstants; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author will + */ +@TestMethodOrder(MethodOrderer.Alphanumeric.class) +public class StatementProxyTest { + + private static List returnValueColumnLabels = Lists.newArrayList("id", "name"); + + private static Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + + private static Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_statement_proxy", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, + 1, "NO", "YES"}, + new Object[] {"", "", "table_statement_proxy", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, + 2, "YES", "NO"}, + }; + + private static Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + private static StatementProxy statementProxy; + + @BeforeAll + public static void init() throws SQLException { + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, + dataSource.getConnection().getConnection()); + + Statement statement = mockDriver.createMockStatement((MockConnection)connectionProxy.getTargetConnection()); + + MockResultSet mockResultSet = new MockResultSet(statement); + ((ResultSetMetaDataBase)mockResultSet.getMetaData()).getColumns().add(new ResultSetMetaDataBase.ColumnMetaData()); + ((MockStatement) statement).setGeneratedKeys(mockResultSet); + + statementProxy = new StatementProxy(connectionProxy, statement); + EnhancedServiceLoader.load(SQLOperateRecognizerHolder.class, JdbcConstants.MYSQL, + SQLOperateRecognizerHolderFactory.class.getClassLoader()); + DruidDelegatingSQLRecognizerFactory recognizerFactory = (DruidDelegatingSQLRecognizerFactory) EnhancedServiceLoader + .load(SQLRecognizerFactory.class, SqlParserType.SQL_PARSER_TYPE_DRUID); + } + + @AfterEach + public void clear() throws SQLException { + statementProxy.clearBatch(); + } + + @Test + public void testStatementProxy() { + Assertions.assertNotNull(statementProxy); + } + + @Test + public void testGetConnectionProxy() { + Assertions.assertNotNull(statementProxy.getConnectionProxy()); + } + + @Test + public void testExecute() throws SQLException { + String sql = "select * from table_statment_proxy"; + Assertions.assertNotNull(statementProxy.executeQuery(sql)); + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(sql)); + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS)); + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(sql, new int[]{1})); + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(sql, new String[]{"id"})); + Assertions.assertDoesNotThrow(() -> statementProxy.execute(sql)); + Assertions.assertDoesNotThrow(() -> statementProxy.execute(sql, Statement.RETURN_GENERATED_KEYS)); + Assertions.assertDoesNotThrow(() -> statementProxy.execute(sql, new int[]{1})); + Assertions.assertDoesNotThrow(() -> statementProxy.execute(sql, new String[]{"id"})); + Assertions.assertDoesNotThrow(() -> statementProxy.executeBatch()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + } + + @Test + public void testGetTargetStatement() { + Assertions.assertNotNull(statementProxy.getTargetStatement()); + } + + @Test + public void testGetTargetSQL() throws SQLException{ + String qrySql = "select * from table_statment_proxy"; + Assertions.assertNotNull(statementProxy.executeQuery(qrySql)); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + Assertions.assertNull(statementProxy.getTargetSQL()); + + String insertSql = "insert into t(id) values (?)"; + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(insertSql, new int[]{1})); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + Assertions.assertNull(statementProxy.getTargetSQL()); + + String updateSql = "update t set t.x=? where t.id=?"; + Assertions.assertDoesNotThrow(() -> statementProxy.executeUpdate(updateSql, new int[]{1})); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + Assertions.assertNull(statementProxy.getTargetSQL()); + + statementProxy.addBatch("insert into t(id) values (1)"); + statementProxy.addBatch("insert into t(id) values (2)"); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + Assertions.assertNull(statementProxy.getTargetSQL()); + + statementProxy.addBatch("update t set t.x = x+1 where t.id = 1"); + statementProxy.addBatch("update t set t.x = x+1 where t.id = 2"); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + Assertions.assertNull(statementProxy.getTargetSQL()); + + statementProxy.addBatch("delete from t where t.id = 1"); + statementProxy.addBatch("delete from t where t.id = 2"); + Assertions.assertNotNull(statementProxy.getTargetSQL()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + } + + @Test + public void testMaxFieldSize() throws SQLException { + statementProxy.setMaxFieldSize(1); + Assertions.assertEquals(1, statementProxy.getMaxFieldSize()); + } + + @Test + public void testMaxRows() throws SQLException { + statementProxy.setMaxRows(1); + Assertions.assertEquals(1, statementProxy.getMaxRows()); + } + + @Test + public void testEscapeProcessing() throws SQLException { + Assertions.assertDoesNotThrow(() -> statementProxy.setEscapeProcessing(true)); + } + + @Test + public void testQueryTimeout() throws SQLException { + statementProxy.setQueryTimeout(1); + Assertions.assertEquals(1, statementProxy.getQueryTimeout()); + } + + @Test + public void testCancel() { + Assertions.assertDoesNotThrow(() -> statementProxy.cancel()); + } + + @Test + public void testWarnings() throws SQLException { + Assertions.assertNull(statementProxy.getWarnings()); + statementProxy.clearWarnings(); + Assertions.assertNull(statementProxy.getWarnings()); + } + + @Test + public void testCursorName() { + Assertions.assertDoesNotThrow(() -> statementProxy.setCursorName("x")); + } + + @Test + public void testResultSet() throws SQLException { + Assertions.assertNotNull(statementProxy.getUpdateCount()); + } + + @Test + public void testUpdateCount() throws SQLException { + Assertions.assertEquals(0, statementProxy.getUpdateCount()); + } + + @Test + public void testMoreResults() throws SQLException { + Assertions.assertFalse(statementProxy.getMoreResults()); + } + + @Test + public void testFetchDirection() throws SQLException { + statementProxy.setFetchDirection(1); + Assertions.assertEquals(1, statementProxy.getFetchDirection()); + } + + @Test + public void testFetchSize() throws SQLException { + statementProxy.setFetchSize(1); + Assertions.assertEquals(1, statementProxy.getFetchSize()); + } + + @Test + public void testResultSetConcurrency() throws SQLException { + Assertions.assertEquals(0, statementProxy.getResultSetConcurrency()); + } + + @Test + public void testResultSetType() throws SQLException { + Assertions.assertEquals(0, statementProxy.getResultSetType()); + } + + @Test + public void testBatch() throws SQLException { + statementProxy.addBatch("update t set x = 'x' where id = 1"); + Assertions.assertDoesNotThrow(() -> statementProxy.executeBatch()); + Assertions.assertDoesNotThrow(() -> statementProxy.clearBatch()); + } + + @Test + public void testGetMoreResults() throws SQLException { + Assertions.assertFalse(statementProxy.getMoreResults(1)); + } + + @Test + public void testGetGeneratedKeys() { + Assertions.assertDoesNotThrow(() -> statementProxy.getGeneratedKeys()); + } + + @Test + public void testGetResultSetHoldability() { + Assertions.assertDoesNotThrow(() -> statementProxy.getResultSetHoldability()); + } + + @Test + public void testIsClosed() { + Assertions.assertDoesNotThrow(() -> statementProxy.isClosed()); + } + + @Test + public void testPoolable() throws SQLException { + statementProxy.setPoolable(true); + Assertions.assertTrue(statementProxy.isPoolable()); + } + + @Test + public void testCloseOnCompletion() { + Assertions.assertThrows(SQLFeatureNotSupportedException.class, () -> statementProxy.closeOnCompletion()); + Assertions.assertThrows(SQLFeatureNotSupportedException.class, () -> statementProxy.isCloseOnCompletion()); + } + + @Test + public void testWrap() throws SQLException { + Assertions.assertDoesNotThrow(() -> statementProxy.unwrap(String.class)); + Assertions.assertFalse(statementProxy.isWrapperFor(String.class)); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutorTest.java new file mode 100644 index 0000000..a5ccdb7 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/AbstractDMLBaseExecutorTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.rm.datasource.ConnectionContext; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.exec.mysql.MySQLInsertExecutor; +import io.seata.rm.datasource.exec.oracle.OracleInsertExecutor; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.Connection; +import java.util.Arrays; + +/** + * AbstractDMLBaseExecutor test + * + * @author ggndnn + */ +public class AbstractDMLBaseExecutorTest { + private ConnectionProxy connectionProxy; + + private AbstractDMLBaseExecutor executor; + + private Field branchRollbackFlagField; + + @BeforeEach + public void initBeforeEach() throws Exception { + branchRollbackFlagField = ConnectionProxy.LockRetryPolicy.class.getDeclaredField("LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(branchRollbackFlagField, branchRollbackFlagField.getModifiers() & ~Modifier.FINAL); + branchRollbackFlagField.setAccessible(true); + boolean branchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + Assertions.assertTrue(branchRollbackFlag); + + Connection targetConnection = Mockito.mock(Connection.class); + connectionProxy = Mockito.mock(ConnectionProxy.class); + Mockito.doThrow(new LockConflictException()) + .when(connectionProxy).commit(); + Mockito.when(connectionProxy.getAutoCommit()) + .thenReturn(Boolean.TRUE); + Mockito.when(connectionProxy.getTargetConnection()) + .thenReturn(targetConnection); + Mockito.when(connectionProxy.getContext()) + .thenReturn(new ConnectionContext()); + PreparedStatementProxy statementProxy = Mockito.mock(PreparedStatementProxy.class); + Mockito.when(statementProxy.getConnectionProxy()) + .thenReturn(connectionProxy); + StatementCallback statementCallback = Mockito.mock(StatementCallback.class); + SQLInsertRecognizer sqlInsertRecognizer = Mockito.mock(SQLInsertRecognizer.class); + TableMeta tableMeta = Mockito.mock(TableMeta.class); + executor = Mockito.spy(new MySQLInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + Mockito.doReturn(tableMeta) + .when(executor).getTableMeta(); + TableRecords tableRecords = new TableRecords(); + Mockito.doReturn(tableRecords) + .when(executor).beforeImage(); + Mockito.doReturn(tableRecords) + .when(executor).afterImage(tableRecords); + } + + @Test + public void testLockRetryPolicyRollbackOnConflict() throws Exception { + boolean oldBranchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + branchRollbackFlagField.set(null, true); + Assertions.assertThrows(LockWaitTimeoutException.class, executor::execute); + Mockito.verify(connectionProxy.getTargetConnection(), Mockito.atLeastOnce()) + .rollback(); + Mockito.verify(connectionProxy, Mockito.never()).rollback(); + branchRollbackFlagField.set(null, oldBranchRollbackFlag); + } + + @Test + public void testLockRetryPolicyNotRollbackOnConflict() throws Throwable { + boolean oldBranchRollbackFlag = (boolean) branchRollbackFlagField.get(null); + branchRollbackFlagField.set(null, false); + Assertions.assertThrows(LockConflictException.class, executor::execute); + Mockito.verify(connectionProxy.getTargetConnection(), Mockito.times(1)).rollback(); + Mockito.verify(connectionProxy, Mockito.never()).rollback(); + branchRollbackFlagField.set(null, oldBranchRollbackFlag); + } + + @Test + public void testOnlySupportMysqlWhenUseMultiPk(){ + Mockito.when(connectionProxy.getContext()) + .thenReturn(new ConnectionContext()); + PreparedStatementProxy statementProxy = Mockito.mock(PreparedStatementProxy.class); + Mockito.when(statementProxy.getConnectionProxy()) + .thenReturn(connectionProxy); + StatementCallback statementCallback = Mockito.mock(StatementCallback.class); + SQLInsertRecognizer sqlInsertRecognizer = Mockito.mock(SQLInsertRecognizer.class); + TableMeta tableMeta = Mockito.mock(TableMeta.class); + executor = Mockito.spy(new OracleInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + Mockito.when(executor.getDbType()).thenReturn(JdbcConstants.ORACLE); + Mockito.doReturn(tableMeta).when(executor).getTableMeta(); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList("id","userCode")); + Assertions.assertThrows(NotSupportYetException.class,()-> executor.executeAutoCommitFalse(null)); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BaseTransactionalExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BaseTransactionalExecutorTest.java new file mode 100644 index 0000000..ece25af --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BaseTransactionalExecutorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.core.model.GlobalLockConfig; +import io.seata.rm.GlobalLockExecutor; +import io.seata.rm.GlobalLockTemplate; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.sql.Statement; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class BaseTransactionalExecutorTest { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Test + public void testExecuteWithGlobalLockSet() throws Throwable { + + //initial objects + ConnectionProxy connectionProxy = new ConnectionProxy(null, null); + StatementProxy statementProxy = new StatementProxy<>(connectionProxy, null); + + BaseTransactionalExecutor baseTransactionalExecutor + = new BaseTransactionalExecutor(statementProxy, null, (SQLRecognizer) null) { + @Override + protected Object doExecute(Object... args) { + return null; + } + }; + GlobalLockTemplate template = new GlobalLockTemplate(); + + // not in global lock context + try { + baseTransactionalExecutor.execute(new Object()); + Assertions.assertFalse(connectionProxy.isGlobalLockRequire(), "connection context set!"); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + // in global lock context + template.execute(new GlobalLockExecutor() { + @Override + public Object execute() throws Throwable { + baseTransactionalExecutor.execute(new Object()); + Assertions.assertTrue(connectionProxy.isGlobalLockRequire(), "connection context not set!"); + return null; + } + + @Override + public GlobalLockConfig getGlobalLockConfig() { + return null; + } + }); + } + + @Test + public void testBuildLockKey() { + //build expect data + String tableName = "test_name"; + String fieldOne = "1"; + String fieldTwo = "2"; + String split1 = ":"; + String split2 = ","; + String pkColumnName="id"; + //test_name:1,2 + String buildLockKeyExpect = tableName + split1 + fieldOne + split2 + fieldTwo; + // mock field + Field field1 = mock(Field.class); + when(field1.getValue()).thenReturn(fieldOne); + Field field2 = mock(Field.class); + when(field2.getValue()).thenReturn(fieldTwo); + List> pkRows =new ArrayList<>(); + pkRows.add(Collections.singletonMap(pkColumnName, field1)); + pkRows.add(Collections.singletonMap(pkColumnName, field2)); + + // mock tableMeta + TableMeta tableMeta = mock(TableMeta.class); + when(tableMeta.getTableName()).thenReturn(tableName); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{pkColumnName})); + // mock tableRecords + TableRecords tableRecords = mock(TableRecords.class); + when(tableRecords.getTableMeta()).thenReturn(tableMeta); + when(tableRecords.size()).thenReturn(pkRows.size()); + when(tableRecords.pkRows()).thenReturn(pkRows); + // mock executor + BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class); + when(executor.buildLockKey(tableRecords)).thenCallRealMethod(); + when(executor.getTableMeta()).thenReturn(tableMeta); + assertThat(executor.buildLockKey(tableRecords)).isEqualTo(buildLockKeyExpect); + } + + @Test + public void testBuildLockKeyWithMultiPk() { + //build expect data + String tableName = "test_name"; + String pkOneValue1 = "1"; + String pkOneValue2 = "2"; + String pkTwoValue1 = "one"; + String pkTwoValue2 = "two"; + String split1 = ":"; + String split2 = ","; + String split3 = "_"; + String pkOneColumnName="id"; + String pkTwoColumnName="userId"; + //test_name:1_one,2_two + String buildLockKeyExpect = tableName + split1 + pkOneValue1+ split3 + pkTwoValue1 + split2 + pkOneValue2 + split3 + pkTwoValue2; + // mock field + Field pkOneField1 = mock(Field.class); + when(pkOneField1.getValue()).thenReturn(pkOneValue1); + Field pkOneField2 = mock(Field.class); + when(pkOneField2.getValue()).thenReturn(pkOneValue2); + Field pkTwoField1 = mock(Field.class); + when(pkTwoField1.getValue()).thenReturn(pkTwoValue1); + Field pkTwoField2 = mock(Field.class); + when(pkTwoField2.getValue()).thenReturn(pkTwoValue2); + List> pkRows =new ArrayList<>(); + Map row1 = new HashMap() {{ + put(pkOneColumnName, pkOneField1); + put(pkTwoColumnName, pkTwoField1); + }}; + pkRows.add(row1); + Map row2 = new HashMap() {{ + put(pkOneColumnName, pkOneField2); + put(pkTwoColumnName, pkTwoField2); + }}; + pkRows.add(row2); + + // mock tableMeta + TableMeta tableMeta = mock(TableMeta.class); + when(tableMeta.getTableName()).thenReturn(tableName); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{pkOneColumnName,pkTwoColumnName})); + // mock tableRecords + TableRecords tableRecords = mock(TableRecords.class); + when(tableRecords.getTableMeta()).thenReturn(tableMeta); + when(tableRecords.size()).thenReturn(pkRows.size()); + when(tableRecords.pkRows()).thenReturn(pkRows); + // mock executor + BaseTransactionalExecutor executor = mock(BaseTransactionalExecutor.class); + when(executor.buildLockKey(tableRecords)).thenCallRealMethod(); + when(executor.getTableMeta()).thenReturn(tableMeta); + assertThat(executor.buildLockKey(tableRecords)).isEqualTo(buildLockKeyExpect); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BatchInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BatchInsertExecutorTest.java new file mode 100644 index 0000000..4f3cddb --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/BatchInsertExecutorTest.java @@ -0,0 +1,537 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import com.alibaba.druid.util.JdbcConstants; +import io.seata.common.exception.NotSupportYetException; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.exec.mysql.MySQLInsertExecutor; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.struct.Null; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.SQLException; +import java.util.*; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +/** + * batch insert executor test + * + * @author zjinlei + */ +public class BatchInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final List PK_VALUES = Arrays.asList(100000001, 100000002, 100000003, 100000004, 100000005); + + + private PreparedStatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private TableMeta tableMeta; + + private MySQLInsertExecutor insertExecutor; + + private final int pkIndex = 1; + private HashMap pkIndexMap; + + @BeforeEach + public void init() { + ConnectionProxy connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.MYSQL); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + StatementCallback statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new MySQLInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndex); + }}; + + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + } + + @Test + public void testGetPkValuesByColumnOfJDBC() throws SQLException { + mockInsertColumns(); + mockParameters(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.addAll(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByColumnAndAllRefOfJDBC() throws SQLException { + mockInsertColumns(); + mockParametersWithAllRefOfJDBC(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(ID_COLUMN)); + List pkValues = new ArrayList<>(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValues,pkValuesMap.get(ID_COLUMN) ); + } + + @Test + public void testGetPkValuesByColumnAndPkRefOfJDBC() throws SQLException { + mockInsertColumns(); + mockParametersWithPkRefOfJDBC(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.addAll(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByColumnAndPkUnRefOfJDBC() throws SQLException { + mockInsertColumns(); + int pkId = PK_VALUES.get(0); + mockParametersWithPkUnRefOfJDBC(pkId); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.add(pkId); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + //----------------mysql batch values (),(),()------------------------ + + @Test + public void testGetPkValuesByColumnAndAllRefOfMysql() throws SQLException { + mockInsertColumns(); + mockParametersAllRefOfMysql(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.addAll(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByColumnAndPkRefOfMysql() throws SQLException { + mockInsertColumns(); + mockParametersWithPkRefOfMysql(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.addAll(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByColumnAndPkUnRefOfMysql() throws SQLException { + mockInsertColumns(); + mockParametersWithPkUnRefOfMysql(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.addAll(PK_VALUES); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesMap.keySet(), tableMeta.getPrimaryKeyOnlyName()); + Assertions.assertIterableEquals(pkValuesMap.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValues_NotSupportYetException() { + Assertions.assertThrows(NotSupportYetException.class, () -> { + mockInsertColumns(); + mockParameters_with_number_and_insertRows_with_placeholde_null(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + insertExecutor.getPkValuesByColumn(); + }); + } + + private void mockParameters_with_null_and_insertRows_with_placeholder_null() { + Map> paramters = new HashMap<>(5); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(Null.get()); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userId2"); + ArrayList arrayList4 = new ArrayList<>(); + arrayList4.add("userName2"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + paramters.put(5, arrayList4); + when(statementProxy.getParameters()).thenReturn(paramters); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "?", "userStatus1")); + insertRows.add(Arrays.asList("?", Null.get(), "?", "userStatus2")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + } + + private void mockParameters_with_number_and_insertRows_with_placeholde_null() { + Map> paramters = new HashMap<>(5); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(PK_VALUES.get(0)); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userId2"); + ArrayList arrayList4 = new ArrayList<>(); + arrayList4.add("userName2"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + paramters.put(5, arrayList4); + when(statementProxy.getParameters()).thenReturn(paramters); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "?", "userStatus1")); + insertRows.add(Arrays.asList("?", Null.get(), "?", "userStatus2")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(USER_ID_COLUMN); + columns.add(ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private void mockParameters() { + int PK_INDEX = 1; + Map> paramters = new HashMap<>(); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + arrayList0.add("userId2"); + arrayList0.add("userId3"); + arrayList0.add("userId4"); + arrayList0.add("userId5"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(PK_VALUES.get(0)); + arrayList1.add(PK_VALUES.get(1)); + arrayList1.add(PK_VALUES.get(2)); + arrayList1.add(PK_VALUES.get(3)); + arrayList1.add(PK_VALUES.get(4)); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + arrayList2.add("userName2"); + arrayList2.add("userName3"); + arrayList2.add("userName4"); + arrayList2.add("userName5"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + arrayList3.add("userStatus2"); + arrayList3.add("userStatus3"); + arrayList3.add("userStatus4"); + arrayList3.add("userStatus5"); + + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + when(statementProxy.getParamsByIndex(PK_INDEX)).thenReturn(paramters.get(PK_INDEX + 1)); + } + + private void mockParametersAllRefOfMysql() { + + Map> paramters = new HashMap(20); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add(100000001); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userName1"); + ArrayList arrayList4 = new ArrayList<>(); + arrayList4.add("userStatus1"); + + ArrayList arrayList5 = new ArrayList<>(); + arrayList5.add("userId2"); + ArrayList arrayList6 = new ArrayList<>(); + arrayList6.add(100000002); + ArrayList arrayList7 = new ArrayList<>(); + arrayList7.add("userName2"); + ArrayList arrayList8 = new ArrayList<>(); + arrayList8.add("userStatus2"); + + ArrayList arrayList9 = new ArrayList<>(); + arrayList9.add("userId3"); + ArrayList arrayList10 = new ArrayList<>(); + arrayList10.add(100000003); + ArrayList arrayList11 = new ArrayList<>(); + arrayList11.add("userName3"); + ArrayList arrayList12 = new ArrayList<>(); + arrayList12.add("userStatus3"); + + ArrayList arrayList13 = new ArrayList<>(); + arrayList13.add("userId4"); + ArrayList arrayList14 = new ArrayList<>(); + arrayList14.add(100000004); + ArrayList arrayList15 = new ArrayList<>(); + arrayList15.add("userName4"); + ArrayList arrayList16 = new ArrayList<>(); + arrayList16.add("userStatus4"); + + ArrayList arrayList17 = new ArrayList<>(); + arrayList17.add("userId5"); + ArrayList arrayList18 = new ArrayList<>(); + arrayList18.add(100000005); + ArrayList arrayList19 = new ArrayList<>(); + arrayList19.add("userName5"); + ArrayList arrayList20 = new ArrayList<>(); + arrayList20.add("userStatus5"); + + + paramters.put(1,arrayList1); + paramters.put(2,arrayList2); + paramters.put(3,arrayList3); + paramters.put(4,arrayList4); + paramters.put(5,arrayList5); + paramters.put(6,arrayList6); + paramters.put(7,arrayList7); + paramters.put(8,arrayList8); + paramters.put(9,arrayList9); + paramters.put(10,arrayList10); + paramters.put(11,arrayList11); + paramters.put(12,arrayList12); + paramters.put(13,arrayList13); + paramters.put(14,arrayList14); + paramters.put(15,arrayList15); + paramters.put(16,arrayList16); + paramters.put(17,arrayList17); + paramters.put(18,arrayList18); + paramters.put(19,arrayList19); + paramters.put(20,arrayList20); + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + when(statementProxy.getParameters()).thenReturn(paramters); + } + + private void mockParametersWithPkRefOfMysql() { + + Map> paramters = new HashMap<>(10); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add(100000001); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userId2"); + ArrayList arrayList4 = new ArrayList<>(); + arrayList4.add(100000002); + ArrayList arrayList5 = new ArrayList<>(); + arrayList5.add("userId3"); + ArrayList arrayList6 = new ArrayList<>(); + arrayList6.add(100000003); + ArrayList arrayList7 = new ArrayList<>(); + arrayList7.add("userId4"); + ArrayList arrayList8 = new ArrayList<>(); + arrayList8.add(100000004); + ArrayList arrayList9 = new ArrayList<>(); + arrayList9.add("userId5"); + ArrayList arrayList10 = new ArrayList<>(); + arrayList10.add(100000005); + paramters.put(1,arrayList1); + paramters.put(2,arrayList2); + paramters.put(3,arrayList3); + paramters.put(4,arrayList4); + paramters.put(5,arrayList5); + paramters.put(6,arrayList6); + paramters.put(7,arrayList7); + paramters.put(8,arrayList8); + paramters.put(9,arrayList9); + paramters.put(10,arrayList10); + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "1", "11")); + insertRows.add(Arrays.asList("?", "?", "2", "22")); + insertRows.add(Arrays.asList("?", "?", "3", "33")); + insertRows.add(Arrays.asList("?", "?", "4", "44")); + insertRows.add(Arrays.asList("?", "?", "5", "55")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + when(statementProxy.getParameters()).thenReturn(paramters); + } + + private void mockParametersWithPkUnRefOfMysql() { + + Map> paramters = new HashMap<>(10); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add(100000001); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userId2"); + ArrayList arrayList4 = new ArrayList<>(); + arrayList4.add(100000002); + ArrayList arrayList5 = new ArrayList<>(); + arrayList5.add("userId3"); + ArrayList arrayList6 = new ArrayList<>(); + arrayList6.add(100000003); + ArrayList arrayList7 = new ArrayList<>(); + arrayList7.add("userId4"); + ArrayList arrayList8 = new ArrayList<>(); + arrayList8.add(100000004); + ArrayList arrayList9 = new ArrayList<>(); + arrayList9.add("userId5"); + ArrayList arrayList10 = new ArrayList<>(); + arrayList10.add(100000005); + paramters.put(1,arrayList1); + paramters.put(2,arrayList2); + paramters.put(3,arrayList3); + paramters.put(4,arrayList4); + paramters.put(5,arrayList5); + paramters.put(6,arrayList6); + paramters.put(7,arrayList7); + paramters.put(8,arrayList8); + paramters.put(9,arrayList9); + paramters.put(10,arrayList10); + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", 100000001, "?", "1")); + insertRows.add(Arrays.asList("?", 100000002, "?", "2")); + insertRows.add(Arrays.asList("?", 100000003, "?", "3")); + insertRows.add(Arrays.asList("?", 100000004, "?", "4")); + insertRows.add(Arrays.asList("?", 100000005, "?", "5")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + } + + + private void mockParametersWithAllRefOfJDBC() { + int PK_INDEX = 1; + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + arrayList0.add("userId2"); + arrayList0.add("userId3"); + arrayList0.add("userId4"); + arrayList0.add("userId5"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(PK_VALUES.get(0)); + arrayList1.add(PK_VALUES.get(1)); + arrayList1.add(PK_VALUES.get(2)); + arrayList1.add(PK_VALUES.get(3)); + arrayList1.add(PK_VALUES.get(4)); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + arrayList2.add("userName2"); + arrayList2.add("userName3"); + arrayList2.add("userName4"); + arrayList2.add("userName5"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + arrayList3.add("userStatus2"); + arrayList3.add("userStatus3"); + arrayList3.add("userStatus4"); + arrayList3.add("userStatus5"); + paramters.put(1,arrayList0); + paramters.put(2,arrayList1); + paramters.put(3,arrayList2); + paramters.put(4,arrayList3); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "?", "?")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(statementProxy.getParamsByIndex(PK_INDEX)).thenReturn(paramters.get(PK_INDEX + 1)); + doReturn(insertRows).when(sqlInsertRecognizer).getInsertRows(pkIndexMap.values()); + } + + + private void mockParametersWithPkRefOfJDBC() { + int PK_INDEX = 1; + Map> paramters = new HashMap<>(2); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + arrayList0.add("userId2"); + arrayList0.add("userId3"); + arrayList0.add("userId4"); + arrayList0.add("userId5"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(PK_VALUES.get(0)); + arrayList1.add(PK_VALUES.get(1)); + arrayList1.add(PK_VALUES.get(2)); + arrayList1.add(PK_VALUES.get(3)); + arrayList1.add(PK_VALUES.get(4)); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", "?", "userName1", "userStatus1")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + when(statementProxy.getParamsByIndex(PK_INDEX)).thenReturn(paramters.get(PK_INDEX + 1)); + } + + + private void mockParametersWithPkUnRefOfJDBC(int pkId) { + Map> paramters = new HashMap<>(2); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add("userId1"); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userName1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + + List> insertRows = new ArrayList<>(); + insertRows.add(Arrays.asList("?", pkId, "?", "userStatus")); + when(statementProxy.getParameters()).thenReturn(paramters); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(insertRows); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/DeleteExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/DeleteExecutorTest.java new file mode 100644 index 0000000..838e73a --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/DeleteExecutorTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.util.JdbcConstants; +import com.google.common.collect.Lists; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.druid.mysql.MySQLDeleteRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class DeleteExecutorTest { + + private static DeleteExecutor deleteExecutor; + + private static StatementProxy statementProxy; + + @BeforeAll + public static void init() { + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_delete_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_delete_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + try { + Field field = dataSourceProxy.getClass().getDeclaredField("dbType"); + field.setAccessible(true); + field.set(dataSourceProxy, "mysql"); + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + statementProxy = new StatementProxy(connectionProxy, mockStatement); + } catch (Exception e) { + throw new RuntimeException("init failed"); + } + String sql = "delete from t where id = 1"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLDeleteRecognizer recognizer = new MySQLDeleteRecognizer(sql, asts.get(0)); + deleteExecutor = new DeleteExecutor(statementProxy, (statement, args) -> { + return null; + }, recognizer); + } + + @Test + public void testBeforeImage() throws SQLException { + Assertions.assertNotNull(deleteExecutor.beforeImage()); + + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLDeleteRecognizer recognizer = new MySQLDeleteRecognizer(sql, asts.get(0)); + deleteExecutor = new DeleteExecutor(statementProxy, (statement, args) -> null, recognizer); + Assertions.assertNotNull(deleteExecutor.beforeImage()); + } + + @Test + public void testAfterImage() throws SQLException { + TableRecords tableRecords = deleteExecutor.beforeImage(); + Assertions.assertEquals(0, deleteExecutor.afterImage(tableRecords).size()); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/LockRetryControllerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/LockRetryControllerTest.java new file mode 100644 index 0000000..e4bd041 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/LockRetryControllerTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.DefaultValues; +import io.seata.config.ConfigurationChangeEvent; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.core.context.GlobalLockConfigHolder; +import io.seata.core.model.GlobalLockConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author selfishlover + */ +public class LockRetryControllerTest { + + private GlobalLockConfig config; + + private final int defaultRetryInternal = DefaultValues.DEFAULT_CLIENT_LOCK_RETRY_INTERVAL; + private final int defaultRetryTimes = DefaultValues.DEFAULT_CLIENT_LOCK_RETRY_TIMES; + + @BeforeEach + void setUp() { + config = new GlobalLockConfig(); + config.setLockRetryInternal(10); + config.setLockRetryTimes(3); + GlobalLockConfigHolder.setAndReturnPrevious(config); + } + + @Test + void testRetryNotExceeded() { + LockRetryController controller = new LockRetryController(); + assertDoesNotThrow(() -> { + for (int times = 0; times < config.getLockRetryTimes(); times++) { + controller.sleep(new RuntimeException("test")); + } + }, "should not throw anything when retry not exceeded"); + } + + @Test + void testRetryExceeded() { + LockRetryController controller = new LockRetryController(); + assertThrows(LockWaitTimeoutException.class, () -> { + for (int times = 0; times <= config.getLockRetryTimes(); times++) { + controller.sleep(new RuntimeException("test")); + } + }, "should throw LockWaitTimeoutException when retry exceeded"); + } + + @Test + void testNoCustomizedConfig() { + GlobalLockConfigHolder.remove(); + LockRetryController controller = new LockRetryController(); + String message = "should use global config when there is no customized config"; + assertEquals(defaultRetryInternal, controller.getLockRetryInternal(), message); + assertEquals(defaultRetryTimes, controller.getLockRetryTimes(), message); + } + + @Test + void testLockConfigListener() { + LockRetryController.GlobalConfig config = new LockRetryController.GlobalConfig(); + ConfigurationChangeEvent event = new ConfigurationChangeEvent(); + + event.setDataId(ConfigurationKeys.CLIENT_LOCK_RETRY_INTERVAL); + int retryInterval = 100; + event.setNewValue(retryInterval + ""); + config.onChangeEvent(event); + String message1 = "lock config listener fail to update latest value of CLIENT_LOCK_RETRY_INTERVAL"; + assertEquals(retryInterval, config.getGlobalLockRetryInternal(), message1); + + event.setDataId(ConfigurationKeys.CLIENT_LOCK_RETRY_TIMES); + int retryTimes = 5; + event.setNewValue(retryTimes + ""); + config.onChangeEvent(event); + String message2 = "lock config listener fail to update latest value of CLIENT_LOCK_RETRY_TIMES"; + assertEquals(retryTimes, config.getGlobalLockRetryTimes(), message2); + + event.setDataId(ConfigurationKeys.CLIENT_LOCK_RETRY_INTERVAL); + event.setNewValue("not a number"); + config.onChangeEvent(event); + String message3 = "should fallback to default value when receive an illegal config value of CLIENT_LOCK_RETRY_INTERVAL"; + assertEquals(defaultRetryInternal, config.getGlobalLockRetryInternal(), message3); + + event.setDataId(ConfigurationKeys.CLIENT_LOCK_RETRY_TIMES); + event.setNewValue("not a number"); + config.onChangeEvent(event); + String message4 = "should fallback to default value when receive an illegal config value of CLIENT_LOCK_RETRY_TIMES"; + assertEquals(defaultRetryTimes, config.getGlobalLockRetryTimes(), message4); + } + + @AfterEach + void tearDown() { + GlobalLockConfigHolder.remove(); + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MultiExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MultiExecutorTest.java new file mode 100644 index 0000000..32d3a6d --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MultiExecutorTest.java @@ -0,0 +1,213 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.util.JdbcConstants; +import com.google.common.collect.Lists; +import io.seata.common.exception.NotSupportYetException; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.mock.MockExecuteHandlerImpl; +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class MultiExecutorTest { + + private static MultiExecutor executor; + + private static StatementProxy statementProxy; + private static MockDriver mockDriver; + private static ConnectionProxy connectionProxy; + + @BeforeAll + public static void init() throws Throwable { + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] returnValue = new Object[][]{ + new Object[]{1, "Tom"}, + new Object[]{2, "Jack"}, + }; + Object[][] columnMetas = new Object[][]{ + new Object[]{"", "", "table_update_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[]{"", "", "table_update_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][]{ + new Object[]{"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + try { + Field field = dataSourceProxy.getClass().getDeclaredField("dbType"); + field.setAccessible(true); + field.set(dataSourceProxy, "mysql"); + connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + statementProxy = new StatementProxy(connectionProxy, mockStatement); + } catch (Exception e) { + throw new RuntimeException("init failed"); + } + + + } + + @Test + public void testBeforeImageAndAfterImages() throws SQLException { + //same table and same type + String sql = "update table_update_executor_test set name = 'WILL' where id = 1;" + + "update table_update_executor_test set name = 'WILL2' where id = 2"; + List multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + TableRecords beforeImage = executor.beforeImage(); + Map multiSqlGroup = executor.getMultiSqlGroup(); + Map beforeImagesMap = executor.getBeforeImagesMap(); + Assertions.assertEquals(multiSqlGroup.size(), 1); + Assertions.assertEquals(beforeImagesMap.size(), 1); + TableRecords afterImage = executor.afterImage(beforeImage); + Assertions.assertEquals(executor.getAfterImagesMap().size(), 1); + executor.prepareUndoLog(beforeImage, afterImage); + List items = connectionProxy.getContext().getUndoItems(); + Assertions.assertTrue(items.stream().allMatch(t -> Objects.equals(t.getSqlType(), SQLType.UPDATE) && Objects.equals(t.getTableName(), "table_update_executor_test"))); + Assertions.assertEquals(items.size(), 1); + connectionProxy.getContext().reset(); + + + //same table delete + sql = "delete from table_update_executor_test where id = 2;" + + "delete from table_update_executor_test where id = 3"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + beforeImage = executor.beforeImage(); + multiSqlGroup = executor.getMultiSqlGroup(); + beforeImagesMap = executor.getBeforeImagesMap(); + Assertions.assertEquals(multiSqlGroup.size(), 1); + Assertions.assertEquals(beforeImagesMap.size(), 1); + afterImage = executor.afterImage(beforeImage); + Assertions.assertEquals(executor.getAfterImagesMap().size(), 1); + executor.prepareUndoLog(beforeImage, afterImage); + items = connectionProxy.getContext().getUndoItems(); + Set itemSet = items.stream().map(t -> t.getTableName()).collect(Collectors.toSet()); + Assertions.assertTrue(itemSet.contains("table_update_executor_test")); + Assertions.assertEquals(items.size(), 1); + connectionProxy.getContext().reset(); + + + //multi table update + sql = "update table_update_executor_test set name = 'WILL' where id = 1;update table_update_executor_test2 set name = 'WILL' where id = 1;update table_update_executor_test2 set name = 'WILL' where id = 3;"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + beforeImage = executor.beforeImage(); + multiSqlGroup = executor.getMultiSqlGroup(); + beforeImagesMap = executor.getBeforeImagesMap(); + Assertions.assertEquals(multiSqlGroup.size(), 2); + Assertions.assertEquals(beforeImagesMap.size(), 2); + afterImage = executor.afterImage(beforeImage); + Assertions.assertEquals(executor.getAfterImagesMap().size(), 2); + executor.prepareUndoLog(beforeImage, afterImage); + items = connectionProxy.getContext().getUndoItems(); + itemSet = items.stream().map(t -> t.getTableName()).collect(Collectors.toSet()); + Assertions.assertTrue(itemSet.contains("table_update_executor_test")); + Assertions.assertTrue(itemSet.contains("table_update_executor_test2")); + Assertions.assertEquals(items.size(), 2); + connectionProxy.getContext().reset(); + + + // multi table delete + sql = "delete from table_update_executor_test2 where id = 2;delete from table_update_executor_test where id = 3;delete from table_update_executor_test where id = 4;delete from table_update_executor_test"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + beforeImage = executor.beforeImage(); + multiSqlGroup = executor.getMultiSqlGroup(); + beforeImagesMap = executor.getBeforeImagesMap(); + Assertions.assertEquals(multiSqlGroup.size(), 2); + Assertions.assertEquals(beforeImagesMap.size(), 2); + afterImage = executor.afterImage(beforeImage); + Assertions.assertEquals(executor.getAfterImagesMap().size(), 2); + executor.prepareUndoLog(beforeImage, afterImage); + items = connectionProxy.getContext().getUndoItems(); + itemSet = items.stream().map(t -> t.getTableName()).collect(Collectors.toSet()); + Assertions.assertTrue(itemSet.contains("table_update_executor_test")); + Assertions.assertTrue(itemSet.contains("table_update_executor_test2")); + Assertions.assertEquals(items.size(), 2); + + // contains limit delete + sql = "delete from table_update_executor_test2 where id = 2;delete from table_update_executor_test2 where id = 2 limit 1;"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + Assertions.assertThrows(NotSupportYetException.class, executor::beforeImage); + + // contains order by and limit delete + sql = "delete from table_update_executor_test2 where id = 2;delete from table_update_executor_test2 where id = 2 order by id desc limit 1;"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + Assertions.assertThrows(NotSupportYetException.class, executor::beforeImage); + + + //contains order by update + sql = "update table_update_executor_test set name = 'WILL' where id = 1;update table_update_executor_test set name = 'WILL' where id = 1 order by id desc;"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + Assertions.assertThrows(NotSupportYetException.class, executor::beforeImage); + + //contains order by and limit update + sql = "update table_update_executor_test set name = 'WILL' where id = 1;update table_update_executor_test set name = 'WILL' where id = 1 order by id desc limit 1;"; + multi = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + executor = new MultiExecutor(statementProxy, (statement, args) -> { + return null; + }, multi); + Assertions.assertThrows(NotSupportYetException.class, executor::beforeImage); + } +} + diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MySQLInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MySQLInsertExecutorTest.java new file mode 100644 index 0000000..9f28a7b --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/MySQLInsertExecutorTest.java @@ -0,0 +1,658 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import com.mysql.jdbc.ResultSetImpl; +import com.mysql.jdbc.util.ResultSetUtil; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.common.util.ReflectionUtil; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.mysql.MySQLInsertExecutor; +import io.seata.rm.datasource.mock.MockDataSource; +import io.seata.rm.datasource.mock.MockResultSet; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.struct.SqlDefaultExpr; +import io.seata.sqlparser.struct.SqlMethodExpr; +import io.seata.sqlparser.struct.SqlSequenceExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author guoyao, jsbxyyx + */ +public class MySQLInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE = 100; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private TableMeta tableMeta; + + private MySQLInsertExecutor insertExecutor; + + private final int pkIndex = 0; + private HashMap pkIndexMap; + + @BeforeEach + public void init() throws SQLException { + ConnectionProxy connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.MYSQL); + DataSourceProxy dataSourceProxy = new DataSourceProxy(new MockDataSource()); + when(connectionProxy.getDataSourceProxy()).thenReturn(dataSourceProxy); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + when(statementProxy.getTargetStatement()).thenReturn(statementProxy); + + MockResultSet resultSet = new MockResultSet(statementProxy); + resultSet.mockResultSet(Arrays.asList("Variable_name", "Value"), new Object[][]{{"auto_increment_increment", "1"}}); + when(statementProxy.getTargetStatement().executeQuery("SHOW VARIABLES LIKE 'auto_increment_increment'")).thenReturn(resultSet); + + StatementCallback statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new MySQLInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap(){ + { + put(ID_COLUMN, pkIndex); + } + }; + } + + @Test + public void testBeforeImage() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + TableRecords tableRecords = insertExecutor.beforeImage(); + Assertions.assertEquals(tableRecords.size(), 0); + try { + tableRecords.add(new Row()); + } catch (Exception e) { + Assertions.assertTrue(e instanceof UnsupportedOperationException); + } + try { + tableRecords.getTableMeta(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof UnsupportedOperationException); + } + } + + @Test + public void testAfterImage_ByColumn() throws SQLException { + doReturn(true).when(insertExecutor).containsPK(); + Map> pkValuesMap =new HashMap<>(); + pkValuesMap.put("id",Arrays.asList(new Object[]{PK_VALUE})); + doReturn(pkValuesMap).when(insertExecutor).getPkValuesByColumn(); + TableRecords tableRecords = new TableRecords(); + doReturn(tableRecords).when(insertExecutor).buildTableRecords(pkValuesMap); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + TableRecords resultTableRecords = insertExecutor.afterImage(new TableRecords()); + Assertions.assertEquals(resultTableRecords, tableRecords); + } + + @Test + public void testAfterImage_ByAuto() throws SQLException { + doReturn(false).when(insertExecutor).containsPK(); + doReturn(true).when(insertExecutor).containsColumns(); + Map> pkValuesMap =new HashMap<>(); + pkValuesMap.put("id",Arrays.asList(new Object[]{PK_VALUE})); + doReturn(pkValuesMap).when(insertExecutor).getPkValuesByAuto(); + TableRecords tableRecords = new TableRecords(); + doReturn(tableRecords).when(insertExecutor).buildTableRecords(pkValuesMap); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + TableRecords resultTableRecords = insertExecutor.afterImage(new TableRecords()); + Assertions.assertEquals(resultTableRecords, tableRecords); + } + + @Test + public void testAfterImage_Exception() { + Assertions.assertThrows(SQLException.class, () -> { + doReturn(false).when(insertExecutor).containsPK(); + doReturn(true).when(insertExecutor).containsColumns(); + Map> pkValuesMap =new HashMap<>(); + pkValuesMap.put("id",Arrays.asList(new Object[]{PK_VALUE})); + doReturn(pkValuesMap).when(insertExecutor).getPkValuesByAuto(); + doReturn(null).when(insertExecutor).buildTableRecords(pkValuesMap); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + insertExecutor.afterImage(new TableRecords()); + }); + } + + @Test + public void testContainsPK() { + List insertColumns = mockInsertColumns(); + mockInsertRows(); + mockParameters(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.containsPK(insertColumns)).thenReturn(true); + Assertions.assertTrue(insertExecutor.containsPK()); + when(tableMeta.containsPK(insertColumns)).thenReturn(false); + Assertions.assertFalse(insertExecutor.containsPK()); + } + + @Test + public void testGetPkValuesByColumn() throws SQLException { + mockInsertColumns(); + mockInsertRows(); + mockParametersOfOnePk(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValues = new ArrayList<>(); + pkValues.add(PK_VALUE); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + Map> pkValuesList = insertExecutor.getPkValuesByColumn(); + Assertions.assertIterableEquals(pkValuesList.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByColumn_Exception() { + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + mockInsertColumns(); + mockParameters(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + insertExecutor.getPkValuesByColumn(); + }); + } + + @Test + public void testGetPkValuesByColumn_PkValue_Null() throws SQLException { + mockInsertColumns(); + mockInsertRows(); + mockParametersPkWithNull(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + ColumnMeta cm = new ColumnMeta(); + cm.setColumnName(ID_COLUMN); + cm.setIsAutoincrement("YES"); + when(tableMeta.getPrimaryKeyMap()).thenReturn(new HashMap(){{put(ID_COLUMN,cm);}}); + List pkValuesAuto = new ArrayList<>(); + pkValuesAuto.add(PK_VALUE); + //mock getPkValuesByAuto + doReturn(new HashMap>(){{put(ID_COLUMN,pkValuesAuto);}}).when(insertExecutor).getPkValuesByAuto(); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + Map> pkValuesList = insertExecutor.getPkValuesByColumn(); + //pk value = Null so getPkValuesByAuto + verify(insertExecutor).getPkValuesByAuto(); + Assertions.assertIterableEquals(pkValuesList.get(ID_COLUMN), pkValuesAuto); + } + + + @Test + public void testGetPkValuesByAuto_ShouldNeverHappenException() { + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(statementProxy.getTargetStatement()).thenReturn(preparedStatement); + when(preparedStatement.getGeneratedKeys()).thenReturn(mock(ResultSet.class)); + Map columnMetaMap = new HashMap<>(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(false); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + insertExecutor.getPkValuesByAuto(); + }); + } + + @Test + public void testGetPkValuesByAuto_SQLException() { + Assertions.assertThrows(SQLException.class, () -> { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + Map columnMetaMap = new HashMap<>(); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(true); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + when(statementProxy.getGeneratedKeys()).thenThrow(new SQLException()); + insertExecutor.getPkValuesByAuto(); + }); + } + + @Test + public void testGetPkValuesByAuto_SQLException_WarnLog() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + Map columnMetaMap = new HashMap<>(); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(true); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(statementProxy.getTargetStatement()).thenReturn(preparedStatement); + SQLException e = new SQLException("test warn log", MySQLInsertExecutor.ERR_SQL_STATE, 1); + when(statementProxy.getGeneratedKeys()).thenThrow(e); + ResultSet genKeys = mock(ResultSet.class); + when(statementProxy.getTargetStatement().executeQuery("SELECT LAST_INSERT_ID()")).thenReturn(genKeys); + Map> pkValueMap=insertExecutor.getPkValuesByAuto(); + Assertions.assertTrue(pkValueMap.get(ID_COLUMN).isEmpty()); + } + + @Test + public void testGetPkValuesByAuto_GeneratedKeys_NoResult() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + Map columnMetaMap = new HashMap<>(); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(true); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(statementProxy.getTargetStatement()).thenReturn(preparedStatement); + ResultSet resultSet = mock(ResultSet.class); + when(statementProxy.getGeneratedKeys()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + when(resultSet.getObject(1)).thenReturn(PK_VALUE); + Map> pkValues = insertExecutor.getPkValuesByAuto(); + Assertions.assertEquals(pkValues.get(ID_COLUMN).size(),0); + } + + @Test + public void testGetPkValuesByAuto_GeneratedKeys_HasResult() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + Map columnMetaMap = new HashMap<>(); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(true); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(statementProxy.getTargetStatement()).thenReturn(preparedStatement); + ResultSet resultSet = mock(ResultSet.class); + when(statementProxy.getGeneratedKeys()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true).thenReturn(false); + when(resultSet.getObject(1)).thenReturn(PK_VALUE); + List pkValues = new ArrayList<>(); + pkValues.add(PK_VALUE); + Map> pkValuesList = insertExecutor.getPkValuesByAuto(); + Assertions.assertIterableEquals(pkValuesList.get(ID_COLUMN), pkValues); + } + + @Test + public void testGetPkValuesByAuto_ExecuteQuery_HasResult() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + Map columnMetaMap = new HashMap<>(); + columnMetaMap.put(ID_COLUMN, columnMeta); + when(columnMeta.isAutoincrement()).thenReturn(true); + when(tableMeta.getPrimaryKeyMap()).thenReturn(columnMetaMap); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(statementProxy.getTargetStatement()).thenReturn(preparedStatement); + when(statementProxy.getGeneratedKeys()).thenThrow(new SQLException("", MySQLInsertExecutor.ERR_SQL_STATE)); + ResultSet resultSet = mock(ResultSet.class); + when(preparedStatement.executeQuery(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true).thenReturn(false); + when(resultSet.getObject(1)).thenReturn(PK_VALUE); + List pkValues = new ArrayList<>(); + pkValues.add(PK_VALUE); + Map> pkValuesList = insertExecutor.getPkValuesByAuto(); + Assertions.assertIterableEquals(pkValuesList.get(ID_COLUMN), pkValues); + } + + @Test + public void test_getPkIndex() { + mockInsertColumns(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + Assertions.assertEquals(0, insertExecutor.getPkIndex().get(ID_COLUMN)); + } + + + @Test + public void test_checkPkValuesForMultiPk() + { + Map> pkValues = new HashMap<>(); + List pkValues1 = new ArrayList(); + List pkValues2 = new ArrayList(); + pkValues.put("id",pkValues1); + pkValues.put("userCode",pkValues2); + + //all pk support value + pkValues1.add(1); + pkValues2.add(2); + Assertions.assertTrue(insertExecutor.checkPkValuesForMultiPk(pkValues)); + + //supporting one pk is null + pkValues1.clear(); + pkValues2.clear(); + pkValues1.add(Null.get()); + pkValues2.add(2); + Assertions.assertTrue(insertExecutor.checkPkValuesForMultiPk(pkValues)); + + //more one pk is null is not support + pkValues1.clear(); + pkValues2.clear(); + pkValues1.add(Null.get()); + pkValues2.add(Null.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForMultiPk(pkValues)); + + //method is not support at all + pkValues1.clear(); + pkValues2.clear(); + pkValues1.add(SqlMethodExpr.get()); + pkValues2.add(2); + Assertions.assertFalse(insertExecutor.checkPkValuesForMultiPk(pkValues)); + + } + + @Test + public void test_checkPkValues() { + + // ps = true + List pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(Null.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(2); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(new SqlSequenceExpr()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlDefaultExpr.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + // ps = false + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(Null.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(2); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(new SqlSequenceExpr()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertTrue(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlDefaultExpr.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + // not support. + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(Null.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(Null.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(1); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(SqlMethodExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(Null.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(new SqlSequenceExpr()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + + pkValues = new ArrayList<>(); + pkValues.add(new SqlSequenceExpr()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, true)); + + pkValues = new ArrayList<>(); + pkValues.add(SqlMethodExpr.get()); + pkValues.add(new SqlSequenceExpr()); + pkValues.add(SqlDefaultExpr.get()); + Assertions.assertFalse(insertExecutor.checkPkValuesForSinglePk(pkValues, false)); + } + + @Test + public void test_autoGeneratePks() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method method = MySQLInsertExecutor.class.getDeclaredMethod("autoGeneratePks", new Class[]{BigDecimal.class, String.class, Integer.class}); + method.setAccessible(true); + Object resp = method.invoke(insertExecutor, BigDecimal.ONE, "ID", 3); + + Assertions.assertNotNull(resp); + Assertions.assertTrue(resp instanceof Map); + + Map map = (Map) resp; + Assertions.assertEquals(map.size(), 1); + Assertions.assertEquals(map.get("ID").size(), 3); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private void mockParameters() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(PK_VALUE); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + } + + private void mockParametersPkWithNull() { + Map> parameters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + parameters.put(1, arrayList0); + parameters.put(2, arrayList1); + parameters.put(3, arrayList2); + parameters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(parameters); + } + + private void mockParametersOfOnePk() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(PK_VALUE); + paramters.put(1, arrayList1); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + } + + private void mockInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/OracleInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/OracleInsertExecutorTest.java new file mode 100644 index 0000000..c97b46d --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/OracleInsertExecutorTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.oracle.OracleInsertExecutor; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.struct.Null; +import io.seata.sqlparser.struct.SqlSequenceExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author jsbxyyx + */ +public class OracleInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE = 100; + + private ConnectionProxy connectionProxy; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private StatementCallback statementCallback; + + private TableMeta tableMeta; + + private OracleInsertExecutor insertExecutor; + + private final int pkIndex = 0; + private HashMap pkIndexMap; + + @BeforeEach + public void init() { + connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.ORACLE); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new OracleInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndex); + }}; + } + + @Test + public void testPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValuesSeq = new ArrayList<>(); + pkValuesSeq.add(PK_VALUE); + + doReturn(pkValuesSeq).when(insertExecutor).getPkValuesBySequence(expr); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeq); + } + + @Test + public void testPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN}));; + doReturn(Arrays.asList(new Object[]{PK_VALUE})).when(insertExecutor).getGeneratedKeys(); + Map> pkValuesByAuto = insertExecutor.getPkValues(); + + verify(insertExecutor).getGeneratedKeys(); + Assertions.assertEquals(pkValuesByAuto.get(ID_COLUMN), Arrays.asList(new Object[]{PK_VALUE})); + } + + @Test + public void testStatement_pkValueByAuto_NotSupportYetException() throws Exception { + mockInsertColumns(); + mockStatementInsertRows(); + + statementProxy = mock(StatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.ORACLE); + + insertExecutor = Mockito.spy(new OracleInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Map map = new HashMap<>(); + map.put(ID_COLUMN, mock(ColumnMeta.class)); + doReturn(map).when(tableMeta).getPrimaryKeyMap(); + + ResultSet rs = mock(ResultSet.class); + doReturn(rs).when(statementProxy).getGeneratedKeys(); + doReturn(false).when(rs).next(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getGeneratedKeys(); + }); + + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValuesByColumn(); + }); + + } + + + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private SqlSequenceExpr mockParametersPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private void mockParametersPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + private void mockStatementInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList(Null.get(), "xx", "xx", "xx")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PlainExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PlainExecutorTest.java new file mode 100644 index 0000000..7a2e545 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PlainExecutorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.pool.DruidDataSource; +import com.google.common.collect.Lists; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.mock.MockDriver; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PlainExecutorTest { + + private PlainExecutor plainExecutor; + + @BeforeEach + public void init() throws SQLException { + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_plain_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_plain_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + StatementProxy statementProxy = new StatementProxy(connectionProxy, mockStatement); + + plainExecutor = new PlainExecutor(statementProxy, (statement, args) -> null); + } + + @Test + public void testExecute() throws Throwable { + plainExecutor.execute((Object) null); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PostgresqlInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PostgresqlInsertExecutorTest.java new file mode 100644 index 0000000..e1b3838 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/PostgresqlInsertExecutorTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.PreparedStatementProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.struct.SqlDefaultExpr; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.*; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author jsbxyyx + */ +public class PostgresqlInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE = 100; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private TableMeta tableMeta; + + private PostgresqlInsertExecutor insertExecutor; + + private final int pkIndex = 0; + private HashMap pkIndexMap; + + @BeforeEach + public void init() { + ConnectionProxy connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.POSTGRESQL); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + StatementCallback statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new PostgresqlInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() { + { + put(ID_COLUMN, pkIndex); + } + }; + } + + @Test + public void testInsertDefault_ByDefault() throws Exception { + mockInsertColumns(); + mockInsertRows(); + mockParametersPkWithDefault(); + + Map pkMap = new HashMap<>(); + ColumnMeta columnMeta = mock(ColumnMeta.class); + doReturn("nextval('test_id_seq'::regclass)").when(columnMeta).getColumnDef(); + pkMap.put(ID_COLUMN, columnMeta); + doReturn(pkMap).when(tableMeta).getPrimaryKeyMap(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkValuesAuto = new ArrayList<>(); + pkValuesAuto.add(PK_VALUE); + //mock getPkValuesByAuto + doReturn(pkValuesAuto).when(insertExecutor).getGeneratedKeys(); + Map> pkValuesMap = insertExecutor.getPkValuesByColumn(); + //pk value = DEFAULT so getPkValuesByDefault + doReturn(new ArrayList<>()).when(insertExecutor).getPkValuesByDefault(); + + verify(insertExecutor).getPkValuesByDefault(); + Assertions.assertEquals(pkValuesMap.get(ID_COLUMN), pkValuesAuto); + } + + private void mockParametersPkWithDefault() { + Map> parameters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(SqlDefaultExpr.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + parameters.put(1, arrayList0); + parameters.put(2, arrayList1); + parameters.put(3, arrayList2); + parameters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(parameters); + } + + private void mockInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + return columns; + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/SelectForUpdateExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/SelectForUpdateExecutorTest.java new file mode 100644 index 0000000..46d4ca0 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/SelectForUpdateExecutorTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.lang.reflect.Field; +import java.sql.Types; +import java.util.List; +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.util.JdbcConstants; + +import com.google.common.collect.Lists; +import io.seata.core.context.RootContext; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.mock.MockConnectionProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.mock.MockLockConflictConnectionProxy; +import io.seata.sqlparser.druid.mysql.MySQLSelectForUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class SelectForUpdateExecutorTest { + + private static SelectForUpdateExecutor selectForUpdateExecutor; + + private static ConnectionProxy connectionProxy; + + private static StatementProxy statementProxy; + + @BeforeAll + public static void init() { + RootContext.unbind(); + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_select_for_update_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_select_for_update_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + try { + Field field = dataSourceProxy.getClass().getDeclaredField("dbType"); + field.setAccessible(true); + field.set(dataSourceProxy, "mysql"); + connectionProxy = new MockConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + connectionProxy.bind("xid"); + MockStatement mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + statementProxy = new StatementProxy(connectionProxy, mockStatement); + } catch (Exception e) { + throw new RuntimeException("init failed"); + } + + String sql = "select * from table_select_for_update_executor_test where id = 1"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLSelectForUpdateRecognizer recognizer = new MySQLSelectForUpdateRecognizer(sql, asts.get(0)); + selectForUpdateExecutor = new SelectForUpdateExecutor(statementProxy, (statement, args) -> { + return null; + }, recognizer); + } + + @Test + public void testDoExecute() throws Throwable { + Assertions.assertThrows(RuntimeException.class, () -> selectForUpdateExecutor.doExecute((Object) null)); + RootContext.bind("xid"); + Assertions.assertDoesNotThrow(() -> { + selectForUpdateExecutor.doExecute((Object) null); + }); + RootContext.unbind(); + + RootContext.bindGlobalLockFlag(); + Assertions.assertDoesNotThrow(() -> { + selectForUpdateExecutor.doExecute((Object) null); + }); + RootContext.unbindGlobalLockFlag(); + + connectionProxy = new MockLockConflictConnectionProxy(connectionProxy.getDataSourceProxy(), connectionProxy.getTargetConnection()); + statementProxy = new StatementProxy(connectionProxy, statementProxy.getTargetStatement()); + statementProxy.getTargetStatement().getConnection().setAutoCommit(false); + String sql = "select * from dual"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLSelectForUpdateRecognizer recognizer = new MySQLSelectForUpdateRecognizer(sql, asts.get(0)); + selectForUpdateExecutor = new SelectForUpdateExecutor(statementProxy, (statement, args) -> null, recognizer); + + RootContext.bind("xid"); + Assertions.assertThrows(LockWaitTimeoutException.class, () -> selectForUpdateExecutor.doExecute((Object) null)); + RootContext.unbind(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/exec/UpdateExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/UpdateExecutorTest.java new file mode 100644 index 0000000..3327122 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/exec/UpdateExecutorTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.exec; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.util.JdbcConstants; + +import com.google.common.collect.Lists; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.StatementProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.druid.mysql.MySQLUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class UpdateExecutorTest { + + private static UpdateExecutor updateExecutor; + + private static StatementProxy statementProxy; + + @BeforeAll + public static void init() { + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] returnValue = new Object[][] { + new Object[] {1, "Tom"}, + new Object[] {2, "Jack"}, + }; + Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_update_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_update_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(dataSource); + try { + Field field = dataSourceProxy.getClass().getDeclaredField("dbType"); + field.setAccessible(true); + field.set(dataSourceProxy, "mysql"); + ConnectionProxy connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + statementProxy = new StatementProxy(connectionProxy, mockStatement); + } catch (Exception e) { + throw new RuntimeException("init failed"); + } + String sql = "update table_update_executor_test set name = 'WILL'"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLUpdateRecognizer recognizer = new MySQLUpdateRecognizer(sql, asts.get(0)); + updateExecutor = new UpdateExecutor(statementProxy, (statement, args) -> { + return null; + }, recognizer); + } + + @Test + public void testBeforeImage() throws SQLException { + Assertions.assertNotNull(updateExecutor.beforeImage()); + + String sql = "update table_update_executor_test set name = 'WILL' where id = 1"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLUpdateRecognizer recognizer = new MySQLUpdateRecognizer(sql, asts.get(0)); + updateExecutor = new UpdateExecutor(statementProxy, (statement, args) -> null, recognizer); + Assertions.assertNotNull(updateExecutor.beforeImage()); + } + + @Test + public void testAfterImage() throws SQLException { + TableRecords beforeImage = updateExecutor.beforeImage(); + TableRecords afterImage = updateExecutor.afterImage(beforeImage); + Assertions.assertNotNull(afterImage); + + afterImage = updateExecutor.afterImage(new TableRecords()); + Assertions.assertNotNull(afterImage); + + afterImage = updateExecutor.afterImage(null); + Assertions.assertNotNull(afterImage); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockBlob.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockBlob.java new file mode 100644 index 0000000..a92c7e8 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockBlob.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.Blob; +import java.sql.SQLException; + +/** + * @author will + */ +public class MockBlob implements Blob { + + public MockBlob() { + } + + @Override + public long length() throws SQLException { + return 0; + } + + @Override + public byte[] getBytes(long pos, int length) throws SQLException { + return new byte[0]; + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return null; + } + + @Override + public long position(byte[] pattern, long start) throws SQLException { + return 0; + } + + @Override + public long position(Blob pattern, long start) throws SQLException { + return 0; + } + + @Override + public int setBytes(long pos, byte[] bytes) throws SQLException { + return 0; + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { + return 0; + } + + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + return null; + } + + @Override + public void truncate(long len) throws SQLException { + + } + + @Override + public void free() throws SQLException { + + } + + @Override + public InputStream getBinaryStream(long pos, long length) throws SQLException { + return null; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockClob.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockClob.java new file mode 100644 index 0000000..00a1908 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockClob.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.sql.Clob; +import java.sql.SQLException; + +/** + * @author will + */ +public class MockClob implements Clob { + + @Override + public long length() throws SQLException { + return 0; + } + + @Override + public String getSubString(long pos, int length) throws SQLException { + return null; + } + + @Override + public Reader getCharacterStream() throws SQLException { + return new CharArrayReader(new char[0]); + } + + @Override + public InputStream getAsciiStream() throws SQLException { + return new ByteArrayInputStream(new byte[0]); + } + + @Override + public long position(String searchstr, long start) throws SQLException { + return 0; + } + + @Override + public long position(Clob searchstr, long start) throws SQLException { + return 0; + } + + @Override + public int setString(long pos, String str) throws SQLException { + return 0; + } + + @Override + public int setString(long pos, String str, int offset, int len) throws SQLException { + return 0; + } + + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + return null; + } + + @Override + public Writer setCharacterStream(long pos) throws SQLException { + return null; + } + + @Override + public void truncate(long len) throws SQLException { + + } + + @Override + public void free() throws SQLException { + + } + + @Override + public Reader getCharacterStream(long pos, long length) throws SQLException { + return null; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnection.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnection.java new file mode 100644 index 0000000..6b03d52 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnection.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.util.Properties; + +/** + * Mock connection + * @author will + */ +public class MockConnection extends com.alibaba.druid.mock.MockConnection { + + private MockDriver mockDriver; + + /** + * Instantiate a new MockConnection + * @param driver + * @param url + * @param connectProperties + */ + public MockConnection(MockDriver driver, String url, Properties connectProperties) { + super(driver, url, connectProperties); + this.mockDriver = driver; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return new MockDatabaseMetaData(this); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + + } + + @Override + public void rollback() { + + } + + @Override + public void rollback(Savepoint savepoint) { + + } + + @Override + public MockDriver getDriver() { + return mockDriver; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnectionProxy.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnectionProxy.java new file mode 100644 index 0000000..5b287e2 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockConnectionProxy.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.Connection; +import java.sql.SQLException; + +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; + +/** + * @author will + */ +public class MockConnectionProxy extends ConnectionProxy { + /** + * Instantiates a new Connection proxy. + * + * @param dataSourceProxy the data source proxy + * @param targetConnection the target connection + */ + public MockConnectionProxy(DataSourceProxy dataSourceProxy, + Connection targetConnection) { + super(dataSourceProxy, targetConnection); + } + + @Override + public void checkLock(String lockKeys) throws SQLException { + //do nothing + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDataSource.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDataSource.java new file mode 100644 index 0000000..1deebb9 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDataSource.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +/** + * @author wang.liang + */ +public class MockDataSource implements DataSource { + @Override + public Connection getConnection() throws SQLException { + return new MockConnection(new MockDriver(), "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true", null); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + + } + + @Override + public int getLoginTimeout() throws SQLException { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDatabaseMetaData.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDatabaseMetaData.java new file mode 100644 index 0000000..926b89f --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDatabaseMetaData.java @@ -0,0 +1,991 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; + +import com.alibaba.druid.mock.MockStatementBase; + +/** + * @author will + */ +public class MockDatabaseMetaData implements DatabaseMetaData { + + protected MockConnection connection; + + private static List columnMetaColumnLabels = Arrays.asList( + "TABLE_CAT", + "TABLE_SCHEM", + "TABLE_NAME", + "COLUMN_NAME", + "DATA_TYPE", + "TYPE_NAME", + "COLUMN_SIZE", + "DECIMAL_DIGITS", + "NUM_PREC_RADIX", + "NULLABLE", + "REMARKS", + "COLUMN_DEF", + "SQL_DATA_TYPE", + "SQL_DATETIME_SUB", + "CHAR_OCTET_LENGTH", + "ORDINAL_POSITION", + "IS_NULLABLE", + "IS_AUTOINCREMENT" + ); + + private static List indexMetaColumnLabels = Arrays.asList( + "INDEX_NAME", + "COLUMN_NAME", + "NON_UNIQUE", + "INDEX_QUALIFIER", + "TYPE", + "ORDINAL_POSITION", + "ASC_OR_DESC", + "CARDINALITY" + ); + + private static List pkMetaColumnLabels = Arrays.asList( + "PK_NAME" + ); + + private Object[][] columnsMetasReturnValue; + + private Object[][] indexMetasReturnValue; + + private Object[][] pkMetasReturnValue; + + /** + * Instantiate a new MockDatabaseMetaData + */ + public MockDatabaseMetaData(MockConnection connection) { + this.connection = connection; + this.columnsMetasReturnValue = connection.getDriver().getMockColumnsMetasReturnValue(); + this.indexMetasReturnValue = connection.getDriver().getMockIndexMetasReturnValue(); + this.pkMetasReturnValue = connection.getDriver().getMockPkMetasReturnValue(); + } + + @Override + public boolean allProceduresAreCallable() throws SQLException { + return false; + } + + @Override + public boolean allTablesAreSelectable() throws SQLException { + return false; + } + + @Override + public String getURL() throws SQLException { + return this.connection.getUrl(); + } + + @Override + public String getUserName() throws SQLException { + return this.connection.getConnectProperties().getProperty("user"); + } + + @Override + public boolean isReadOnly() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedHigh() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedLow() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedAtStart() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedAtEnd() throws SQLException { + return false; + } + + @Override + public String getDatabaseProductName() throws SQLException { + return null; + } + + @Override + public String getDatabaseProductVersion() throws SQLException { + return null; + } + + @Override + public String getDriverName() throws SQLException { + return null; + } + + @Override + public String getDriverVersion() throws SQLException { + return null; + } + + @Override + public int getDriverMajorVersion() { + return 0; + } + + @Override + public int getDriverMinorVersion() { + return 0; + } + + @Override + public boolean usesLocalFiles() throws SQLException { + return false; + } + + @Override + public boolean usesLocalFilePerTable() throws SQLException { + return false; + } + + @Override + public boolean supportsMixedCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesUpperCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public String getIdentifierQuoteString() throws SQLException { + return null; + } + + @Override + public String getSQLKeywords() throws SQLException { + return null; + } + + @Override + public String getNumericFunctions() throws SQLException { + return null; + } + + @Override + public String getStringFunctions() throws SQLException { + return null; + } + + @Override + public String getSystemFunctions() throws SQLException { + return null; + } + + @Override + public String getTimeDateFunctions() throws SQLException { + return null; + } + + @Override + public String getSearchStringEscape() throws SQLException { + return null; + } + + @Override + public String getExtraNameCharacters() throws SQLException { + return null; + } + + @Override + public boolean supportsAlterTableWithAddColumn() throws SQLException { + return false; + } + + @Override + public boolean supportsAlterTableWithDropColumn() throws SQLException { + return false; + } + + @Override + public boolean supportsColumnAliasing() throws SQLException { + return false; + } + + @Override + public boolean nullPlusNonNullIsNull() throws SQLException { + return false; + } + + @Override + public boolean supportsConvert() throws SQLException { + return false; + } + + @Override + public boolean supportsConvert(int fromType, int toType) throws SQLException { + return false; + } + + @Override + public boolean supportsTableCorrelationNames() throws SQLException { + return false; + } + + @Override + public boolean supportsDifferentTableCorrelationNames() throws SQLException { + return false; + } + + @Override + public boolean supportsExpressionsInOrderBy() throws SQLException { + return false; + } + + @Override + public boolean supportsOrderByUnrelated() throws SQLException { + return false; + } + + @Override + public boolean supportsGroupBy() throws SQLException { + return false; + } + + @Override + public boolean supportsGroupByUnrelated() throws SQLException { + return false; + } + + @Override + public boolean supportsGroupByBeyondSelect() throws SQLException { + return false; + } + + @Override + public boolean supportsLikeEscapeClause() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleResultSets() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleTransactions() throws SQLException { + return false; + } + + @Override + public boolean supportsNonNullableColumns() throws SQLException { + return false; + } + + @Override + public boolean supportsMinimumSQLGrammar() throws SQLException { + return false; + } + + @Override + public boolean supportsCoreSQLGrammar() throws SQLException { + return false; + } + + @Override + public boolean supportsExtendedSQLGrammar() throws SQLException { + return false; + } + + @Override + public boolean supportsANSI92EntryLevelSQL() throws SQLException { + return false; + } + + @Override + public boolean supportsANSI92IntermediateSQL() throws SQLException { + return false; + } + + @Override + public boolean supportsANSI92FullSQL() throws SQLException { + return false; + } + + @Override + public boolean supportsIntegrityEnhancementFacility() throws SQLException { + return false; + } + + @Override + public boolean supportsOuterJoins() throws SQLException { + return false; + } + + @Override + public boolean supportsFullOuterJoins() throws SQLException { + return false; + } + + @Override + public boolean supportsLimitedOuterJoins() throws SQLException { + return false; + } + + @Override + public String getSchemaTerm() throws SQLException { + return null; + } + + @Override + public String getProcedureTerm() throws SQLException { + return null; + } + + @Override + public String getCatalogTerm() throws SQLException { + return null; + } + + @Override + public boolean isCatalogAtStart() throws SQLException { + return false; + } + + @Override + public String getCatalogSeparator() throws SQLException { + return null; + } + + @Override + public boolean supportsSchemasInDataManipulation() throws SQLException { + return false; + } + + @Override + public boolean supportsSchemasInProcedureCalls() throws SQLException { + return false; + } + + @Override + public boolean supportsSchemasInTableDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsSchemasInIndexDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInDataManipulation() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInProcedureCalls() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInIndexDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedDelete() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsSelectForUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsStoredProcedures() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInComparisons() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInExists() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInIns() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInQuantifieds() throws SQLException { + return false; + } + + @Override + public boolean supportsCorrelatedSubqueries() throws SQLException { + return false; + } + + @Override + public boolean supportsUnion() throws SQLException { + return false; + } + + @Override + public boolean supportsUnionAll() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossRollback() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossRollback() throws SQLException { + return false; + } + + @Override + public int getMaxBinaryLiteralLength() throws SQLException { + return 0; + } + + @Override + public int getMaxCharLiteralLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInGroupBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInIndex() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInOrderBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInTable() throws SQLException { + return 0; + } + + @Override + public int getMaxConnections() throws SQLException { + return 0; + } + + @Override + public int getMaxCursorNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxIndexLength() throws SQLException { + return 0; + } + + @Override + public int getMaxSchemaNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxProcedureNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxCatalogNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxRowSize() throws SQLException { + return 0; + } + + @Override + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { + return false; + } + + @Override + public int getMaxStatementLength() throws SQLException { + return 0; + } + + @Override + public int getMaxStatements() throws SQLException { + return 0; + } + + @Override + public int getMaxTableNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxTablesInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxUserNameLength() throws SQLException { + return 0; + } + + @Override + public int getDefaultTransactionIsolation() throws SQLException { + return 0; + } + + @Override + public boolean supportsTransactions() throws SQLException { + return false; + } + + @Override + public boolean supportsTransactionIsolationLevel(int level) throws SQLException { + return false; + } + + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { + return false; + } + + @Override + public boolean supportsDataManipulationTransactionsOnly() throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() throws SQLException { + return false; + } + + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) + throws SQLException { + return null; + } + + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) throws SQLException { + return null; + } + + @Override + public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + throws SQLException { + return null; + } + + @Override + public ResultSet getSchemas() throws SQLException { + return null; + } + + @Override + public ResultSet getCatalogs() throws SQLException { + return null; + } + + @Override + public ResultSet getTableTypes() throws SQLException { + return null; + } + + @Override + public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException { + return new MockResultSet((MockStatementBase)this.connection.createStatement()) + .mockResultSet(columnMetaColumnLabels, columnsMetasReturnValue); + } + + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException { + return null; + } + + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException { + return null; + } + + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException { + return null; + } + + @Override + public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { + return null; + } + + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { + return new MockResultSet(this.connection.createStatement()).mockResultSet(pkMetaColumnLabels, pkMetasReturnValue); + } + + @Override + public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { + return null; + } + + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { + return null; + } + + @Override + public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, + String foreignCatalog, String foreignSchema, String foreignTable) + throws SQLException { + return null; + } + + @Override + public ResultSet getTypeInfo() throws SQLException { + return null; + } + + @Override + public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) + throws SQLException { + return new MockResultSet((MockStatementBase)this.connection.createStatement()) + .mockResultSet(indexMetaColumnLabels, indexMetasReturnValue); + } + + @Override + public boolean supportsResultSetType(int type) throws SQLException { + return false; + } + + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { + return false; + } + + @Override + public boolean ownUpdatesAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean ownDeletesAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean ownInsertsAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean othersUpdatesAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean othersDeletesAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean othersInsertsAreVisible(int type) throws SQLException { + return false; + } + + @Override + public boolean updatesAreDetected(int type) throws SQLException { + return false; + } + + @Override + public boolean deletesAreDetected(int type) throws SQLException { + return false; + } + + @Override + public boolean insertsAreDetected(int type) throws SQLException { + return false; + } + + @Override + public boolean supportsBatchUpdates() throws SQLException { + return false; + } + + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) + throws SQLException { + return null; + } + + @Override + public Connection getConnection() throws SQLException { + return null; + } + + @Override + public boolean supportsSavepoints() throws SQLException { + return true; + } + + @Override + public boolean supportsNamedParameters() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleOpenResults() throws SQLException { + return false; + } + + @Override + public boolean supportsGetGeneratedKeys() throws SQLException { + return false; + } + + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { + return null; + } + + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { + return null; + } + + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern) throws SQLException { + return null; + } + + @Override + public boolean supportsResultSetHoldability(int holdability) throws SQLException { + return false; + } + + @Override + public int getResultSetHoldability() throws SQLException { + return 0; + } + + @Override + public int getDatabaseMajorVersion() throws SQLException { + return 0; + } + + @Override + public int getDatabaseMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getJDBCMajorVersion() throws SQLException { + return 0; + } + + @Override + public int getJDBCMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getSQLStateType() throws SQLException { + return 0; + } + + @Override + public boolean locatorsUpdateCopy() throws SQLException { + return false; + } + + @Override + public boolean supportsStatementPooling() throws SQLException { + return false; + } + + @Override + public RowIdLifetime getRowIdLifetime() throws SQLException { + return null; + } + + @Override + public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { + return null; + } + + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { + return false; + } + + @Override + public boolean autoCommitFailureClosesAllResultSets() throws SQLException { + return false; + } + + @Override + public ResultSet getClientInfoProperties() throws SQLException { + return null; + } + + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException { + return null; + } + + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, + String columnNamePattern) throws SQLException { + return null; + } + + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) throws SQLException { + return null; + } + + @Override + public boolean generatedKeyAlwaysReturned() throws SQLException { + return false; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + public void setColumnsMetasReturnValue(Object[][] columnsMetasReturnValue) { + this.columnsMetasReturnValue = columnsMetasReturnValue; + } + + public void setIndexMetasReturnValue(Object[][] indexMetasReturnValue) { + this.indexMetasReturnValue = indexMetasReturnValue; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDriver.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDriver.java new file mode 100644 index 0000000..f8c1865 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockDriver.java @@ -0,0 +1,180 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Properties; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.mock.handler.MockExecuteHandler; + +import com.google.common.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Mock driver + * @author will + */ +public class MockDriver extends com.alibaba.druid.mock.MockDriver { + + private static final Logger LOGGER = LoggerFactory.getLogger(MockDriver.class); + + /** + * the mock column labels of return value + */ + private List mockReturnValueColumnLabels; + + /** + * the mock value of return value + */ + private Object[][] mockReturnValue; + + /** + * the mock value of columns meta return value + */ + private Object[][] mockColumnsMetasReturnValue; + + /** + * the mock value of index meta return value + */ + private Object[][] mockIndexMetasReturnValue; + + /** + * the mock value of pk meta return value + */ + private Object[][] mockPkMetasReturnValue; + + /** + * the mock execute handler + */ + private MockExecuteHandler mockExecuteHandler; + + public MockDriver() { + this(Lists.newArrayList(), new Object[][]{}, new Object[][]{}, new Object[][]{}, new Object[][]{}); + } + + public MockDriver(Object[][] mockColumnsMetasReturnValue, Object[][] mockIndexMetasReturnValue) { + this(Lists.newArrayList(), new Object[][]{}, mockColumnsMetasReturnValue, mockIndexMetasReturnValue, new Object[][]{}); + } + + public MockDriver(Object[][] mockColumnsMetasReturnValue, Object[][] mockIndexMetasReturnValue, Object[][] mockPkMetasReturnValue) { + this(Lists.newArrayList(), new Object[][]{}, mockColumnsMetasReturnValue, mockIndexMetasReturnValue, mockPkMetasReturnValue); + } + + public MockDriver(List mockReturnValueColumnLabels, Object[][] mockReturnValue, Object[][] mockColumnsMetasReturnValue, Object[][] mockIndexMetasReturnValue) { + this(mockReturnValueColumnLabels, mockReturnValue, mockColumnsMetasReturnValue, mockIndexMetasReturnValue, new Object[][]{}); + } + + /** + * Instantiate a new MockDriver + */ + public MockDriver(List mockReturnValueColumnLabels, Object[][] mockReturnValue, Object[][] mockColumnsMetasReturnValue, Object[][] mockIndexMetasReturnValue, Object[][] mockPkMetasReturnValue) { + this.mockReturnValueColumnLabels = mockReturnValueColumnLabels; + this.mockReturnValue = mockReturnValue; + this.mockColumnsMetasReturnValue = mockColumnsMetasReturnValue; + this.mockIndexMetasReturnValue = mockIndexMetasReturnValue; + this.mockPkMetasReturnValue = mockPkMetasReturnValue; + this.setMockExecuteHandler(new MockExecuteHandlerImpl(mockReturnValueColumnLabels, mockReturnValue, mockColumnsMetasReturnValue)); + } + + /** + * Instantiate a new MockConnection + * @param driver + * @param url + * @param connectProperties + * @return + */ + @Override + public MockConnection createMockConnection(com.alibaba.druid.mock.MockDriver driver, String url, + Properties connectProperties) { + return new MockConnection(this, url, connectProperties); + } + + @Override + public ResultSet executeQuery(MockStatementBase stmt, String sql) throws SQLException { + return this.mockExecuteHandler.executeQuery(stmt, sql); + } + + public MockPreparedStatement createSeataMockPreparedStatement(MockConnection conn, String sql) { + return new MockPreparedStatement(conn, sql); + } + + /** + * mock the return value + * @return + */ + public Object[][] getMockReturnValue() { + return mockReturnValue; + } + + /** + * get the return value + * @param mockReturnValue + */ + public void setMockReturnValue(Object[][] mockReturnValue) { + this.mockReturnValue = mockReturnValue == null ? new Object[][]{} : mockReturnValue; + } + + /** + * mock the return value of columns meta + * @param mockColumnsMetasReturnValue + */ + public void setMockColumnsMetasReturnValue(Object[][] mockColumnsMetasReturnValue) { + this.mockColumnsMetasReturnValue = mockColumnsMetasReturnValue == null ? new Object[][]{} : mockColumnsMetasReturnValue; + } + + /** + * get the return value of columns meta + * @return + */ + public Object[][] getMockColumnsMetasReturnValue() { + return mockColumnsMetasReturnValue; + } + + /** + * mock the return value of index meta + * @param mockIndexMetasReturnValue + */ + public void setMockIndexMetasReturnValue(Object[][] mockIndexMetasReturnValue) { + this.mockIndexMetasReturnValue = mockIndexMetasReturnValue == null ? new Object[][]{} : mockIndexMetasReturnValue; + } + + /** + * get the return value of index meta + * @return + */ + public Object[][] getMockIndexMetasReturnValue() { + return mockIndexMetasReturnValue; + } + + /** + * get the return value of pk meta + * @return + */ + public Object[][] getMockPkMetasReturnValue() { + return mockPkMetasReturnValue; + } + + /** + * set the mock execute handler + * @param mockExecuteHandler + */ + public void setMockExecuteHandler(MockExecuteHandler mockExecuteHandler){ + this.mockExecuteHandler = mockExecuteHandler; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockExecuteHandlerImpl.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockExecuteHandlerImpl.java new file mode 100644 index 0000000..759a0f4 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockExecuteHandlerImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.mock.handler.MockExecuteHandler; + +/** + * @author will + */ +public class MockExecuteHandlerImpl implements MockExecuteHandler { + + /** + * the mock value of return value + */ + private Object[][] mockReturnValue; + + /** + * the mock column labels of return value + */ + private List mockReturnValueColumnLabels; + + /** + * the mock column meta + */ + private Object[][] mockColumnsMetasReturnValue; + + /** + * Instantiate MockExecuteHandlerImpl + * @param mockReturnValue + */ + public MockExecuteHandlerImpl(List mockReturnValueColumnLabels, Object[][] mockReturnValue, Object[][] mockColumnsMetasReturnValue) { + this.mockReturnValueColumnLabels = mockReturnValueColumnLabels; + this.mockReturnValue = mockReturnValue; + this.mockColumnsMetasReturnValue = mockColumnsMetasReturnValue; + } + + @Override + public ResultSet executeQuery(MockStatementBase statement, String sql) throws SQLException { + MockResultSet resultSet = new MockResultSet(statement); + + //mock the return value + resultSet.mockResultSet(mockReturnValueColumnLabels, mockReturnValue); + //mock the rs meta data + resultSet.mockResultSetMetaData(mockColumnsMetasReturnValue); + return resultSet; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockLockConflictConnectionProxy.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockLockConflictConnectionProxy.java new file mode 100644 index 0000000..80f3537 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockLockConflictConnectionProxy.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Savepoint; + +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.exec.LockConflictException; + +/** + * @author will + */ +public class MockLockConflictConnectionProxy extends ConnectionProxy { + /** + * Instantiates a new Connection proxy. + * + * @param dataSourceProxy the data source proxy + * @param targetConnection the target connection + */ + public MockLockConflictConnectionProxy(DataSourceProxy dataSourceProxy, + Connection targetConnection) { + super(dataSourceProxy, targetConnection); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + super.releaseSavepoint(savepoint); + } + + @Override + public void checkLock(String lockKeys) throws SQLException { + throw new LockConflictException("mock lock conflict"); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockParameterMetaData.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockParameterMetaData.java new file mode 100644 index 0000000..a4469a0 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockParameterMetaData.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.ParameterMetaData; +import java.sql.SQLException; + +/** + * @author will + */ +public class MockParameterMetaData implements ParameterMetaData { + + private int parameterCount; + + public MockParameterMetaData(String sql) { + for (int i = 0; i < sql.length(); i++) { + if (sql.charAt(i) == '?') { + parameterCount++; + } + } + } + + @Override + public int getParameterCount() throws SQLException { + return parameterCount; + } + + @Override + public int isNullable(int param) throws SQLException { + return 0; + } + + @Override + public boolean isSigned(int param) throws SQLException { + return false; + } + + @Override + public int getPrecision(int param) throws SQLException { + return 0; + } + + @Override + public int getScale(int param) throws SQLException { + return 0; + } + + @Override + public int getParameterType(int param) throws SQLException { + return 0; + } + + @Override + public String getParameterTypeName(int param) throws SQLException { + return null; + } + + @Override + public String getParameterClassName(int param) throws SQLException { + return null; + } + + @Override + public int getParameterMode(int param) throws SQLException { + return 0; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockPreparedStatement.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockPreparedStatement.java new file mode 100644 index 0000000..7ee88f3 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockPreparedStatement.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.ParameterMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.util.jdbc.PreparedStatementBase; + +/** + * @author will + */ +public class MockPreparedStatement extends PreparedStatementBase implements MockStatementBase { + + private final String sql; + + private ParameterMetaData parameterMetaData; + + public MockPreparedStatement(MockConnection conn, String sql){ + super(conn); + this.sql = sql; + parameterMetaData = new MockParameterMetaData(sql); + } + + @Override + public ResultSet executeQuery() throws SQLException { + MockConnection conn = getConnection(); + + if (conn != null && conn.getDriver() != null) { + return conn.getDriver().executeQuery(this, sql); + } + return null; + } + + @Override + public int executeUpdate() throws SQLException { + return 0; + } + + @Override + public boolean execute() throws SQLException { + return false; + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return this.parameterMetaData; + } + + public MockConnection getConnection() throws SQLException { + return (MockConnection) super.getConnection(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSet.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSet.java new file mode 100644 index 0000000..8f29b2d --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSet.java @@ -0,0 +1,173 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.util.jdbc.ResultSetBase; + +import com.google.common.collect.Lists; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author will + */ +public class MockResultSet extends ResultSetBase { + + private List columnMetas; + + private int rowIndex = -1; + + /** + * the column label + */ + private List columnLabels; + + /** + * the return value + */ + private List rows; + + private static final Logger LOGGER = LoggerFactory.getLogger(MockResultSet.class); + + /** + * Instantiates a new Mock result set. + * @param statement + */ + public MockResultSet(Statement statement) { + super(statement); + this.rows = new ArrayList<>(); + this.columnMetas = Lists.newArrayList(); + } + + /** + * mock result set + * @param mockColumnLabels + * @param mockReturnValue + * @return + */ + public MockResultSet mockResultSet(List mockColumnLabels, Object[][] mockReturnValue){ + this.columnLabels = mockColumnLabels; + for (int i = 0; i < mockReturnValue.length; i++) { + Object[] row = mockReturnValue[i]; + this.getRows().add(row); + } + return this; + } + + public void mockResultSetMetaData(Object[][] mockColumnsMetasReturnValue) { + for (Object[] meta : mockColumnsMetasReturnValue) { + ColumnMeta columnMeta = new ColumnMeta(); + columnMeta.setTableName(meta[2].toString()); + columnMeta.setColumnName(meta[3].toString()); + this.columnMetas.add(columnMeta); + } + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return new MockResultSetMetaData(columnMetas); + } + + public MockResultSetMetaData getMockMetaData() { + return new MockResultSetMetaData(columnMetas); + } + + @Override + public boolean next() throws SQLException { + if (closed) { + throw new SQLException(); + } + + if (rowIndex < rows.size() - 1) { + rowIndex++; + return true; + } + return false; + } + + @Override + public int findColumn(String columnLabel) throws SQLException { + if (columnLabels.indexOf(columnLabel) != -1) { + return columnLabels.indexOf(columnLabel) + 1; + } + if (columnLabels.indexOf(columnLabel.toLowerCase()) != -1) { + return columnLabels.indexOf(columnLabel.toLowerCase()) + 1; + } + if (columnLabels.indexOf(columnLabel.toUpperCase()) != -1) { + return columnLabels.indexOf(columnLabel.toUpperCase()) + 1; + } + return -1; + } + + @Override + public Blob getBlob(String columnLabel) throws SQLException { + return getBlob(findColumn(columnLabel)); + } + + @Override + public Blob getBlob(int columnIndex) throws SQLException { + byte[] bytes = getObjectInternal(columnIndex).toString().getBytes(); + return new MockBlob(); + } + + @Override + public Clob getClob(String columnLabel) throws SQLException { + return getClob(findColumn(columnLabel)); + } + + @Override + public Clob getClob(int columnIndex) throws SQLException { + char[] chars = getObjectInternal(columnIndex).toString().toCharArray(); + return new MockClob(); + } + + public Object getObjectInternal(int columnIndex) { + Object[] row = rows.get(rowIndex); + return row[columnIndex - 1]; + } + + @Override + public boolean previous() throws SQLException { + if (closed) { + throw new SQLException(); + } + + if (rowIndex >= 0) { + rowIndex--; + return true; + } + return false; + } + + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + Object[] row = rows.get(rowIndex); + row[columnIndex - 1] = x; + } + + public List getRows() { + return rows; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSetMetaData.java b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSetMetaData.java new file mode 100644 index 0000000..a39efc1 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/mock/MockResultSetMetaData.java @@ -0,0 +1,170 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.mock; +import io.seata.common.util.ReflectionUtil; +import io.seata.rm.datasource.sql.struct.ColumnMeta; + +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author will + */ +public class MockResultSetMetaData implements ResultSetMetaData { + + private List columns; + + public MockResultSetMetaData(List columns) { + this.columns = columns; + } + + public List getColumns() { + return columns; + } + + @Override + public int getColumnCount() throws SQLException { + return columns.size(); + } + + @Override + public boolean isAutoIncrement(int column) throws SQLException { + return getColumn(column).isAutoincrement(); + } + + @Override + public boolean isCaseSensitive(int column) throws SQLException { + return false; + } + + @Override + public boolean isSearchable(int column) throws SQLException { + return false; + } + + @Override + public boolean isCurrency(int column) throws SQLException { + return false; + } + + @Override + public int isNullable(int column) throws SQLException { + return 0; + } + + @Override + public boolean isSigned(int column) throws SQLException { + return false; + } + + @Override + public int getColumnDisplaySize(int column) throws SQLException { + return 0; + } + + @Override + public String getColumnLabel(int column) throws SQLException { + return null; + } + + @Override + public String getColumnName(int column) throws SQLException { + return getColumn(column).getColumnName(); + } + + @Override + public String getSchemaName(int column) throws SQLException { + return null; + } + + @Override + public int getPrecision(int column) throws SQLException { + return 0; + } + + @Override + public int getScale(int column) throws SQLException { + return 0; + } + + @Override + public String getTableName(int column) throws SQLException { + ColumnMeta columnMeta = getColumn(column); + try { + Object tableName = ReflectionUtil.getFieldValue(columnMeta, "tableName"); + return tableName.toString(); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public String getCatalogName(int column) throws SQLException { + return null; + } + + @Override + public int getColumnType(int column) throws SQLException { + return 0; + } + + @Override + public String getColumnTypeName(int column) throws SQLException { + return null; + } + + @Override + public boolean isReadOnly(int column) throws SQLException { + return false; + } + + @Override + public boolean isWritable(int column) throws SQLException { + return false; + } + + @Override + public boolean isDefinitelyWritable(int column) throws SQLException { + return false; + } + + @Override + public String getColumnClassName(int column) throws SQLException { + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + /** + * get column + * @param column + * @return + */ + public ColumnMeta getColumn(int column){ + return columns.get(column - 1); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/SQLVisitorFactoryTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/SQLVisitorFactoryTest.java new file mode 100644 index 0000000..4b31cc6 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/SQLVisitorFactoryTest.java @@ -0,0 +1,194 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql; + +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.mysql.MySQLDeleteRecognizer; +import io.seata.sqlparser.druid.mysql.MySQLInsertRecognizer; +import io.seata.sqlparser.druid.mysql.MySQLSelectForUpdateRecognizer; +import io.seata.sqlparser.druid.mysql.MySQLUpdateRecognizer; +import io.seata.sqlparser.druid.oracle.OracleDeleteRecognizer; +import io.seata.sqlparser.druid.oracle.OracleInsertRecognizer; +import io.seata.sqlparser.druid.oracle.OracleSelectForUpdateRecognizer; +import io.seata.sqlparser.druid.oracle.OracleUpdateRecognizer; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * The type Sql visitor factory test. + */ +public class SQLVisitorFactoryTest { + /** + * Test sql recognizing. + */ + @Test + public void testSqlRecognizing() { + + //test for ast was null + Assertions.assertThrows(UnsupportedOperationException.class, () -> SQLVisitorFactory.get("", JdbcConstants.MYSQL)); + + //test for mysql insert + String sql = "insert into t(id) values (1)"; + List recognizer = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), MySQLInsertRecognizer.class.getName()); + + //test for mysql delete + sql = "delete from t"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), MySQLDeleteRecognizer.class.getName()); + + //test for mysql update + sql = "update t set a = a"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), MySQLUpdateRecognizer.class.getName()); + + //test for mysql select + sql = "select * from t"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + Assertions.assertNull(recognizer); + + //test for mysql select for update + sql = "select * from t for update"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), MySQLSelectForUpdateRecognizer.class.getName()); + + //test for oracle insert + sql = "insert into t(id) values (1)"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), OracleInsertRecognizer.class.getName()); + + //test for oracle delete + sql = "delete from t"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), OracleDeleteRecognizer.class.getName()); + + //test for oracle update + sql = "update t set a = a"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), OracleUpdateRecognizer.class.getName()); + + //test for oracle select + sql = "select * from t"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + Assertions.assertNull(recognizer); + + //test for oracle select for update + sql = "select * from t for update"; + recognizer = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + Assertions.assertEquals(recognizer.get(0).getClass().getName(), OracleSelectForUpdateRecognizer.class.getName()); + + //test for do not support db + Assertions.assertThrows(EnhancedServiceNotFoundException.class, () -> { + SQLVisitorFactory.get("select * from t", JdbcConstants.DB2); + }); + + + //TEST FOR Multi-SQL + + List sqlRecognizers = null; + //test for mysql insert + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);insert into t(id) values (2)", JdbcConstants.MYSQL); + }); + //test for mysql insert and update + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);update t set a = t;", JdbcConstants.MYSQL); + }); + //test for mysql insert and deleted + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);delete from t where id = 1", JdbcConstants.MYSQL); + }); + //test for mysql delete + sql = "delete from t where id =1 ; delete from t where id = 2"; + sqlRecognizers = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + for (SQLRecognizer sqlRecognizer : sqlRecognizers) { + Assertions.assertEquals(sqlRecognizer.getClass().getName(), MySQLDeleteRecognizer.class.getName()); + } + //test for mysql update + sql = "update t set a = a;update t set a = c;"; + sqlRecognizers = SQLVisitorFactory.get(sql, JdbcConstants.MYSQL); + for (SQLRecognizer sqlRecognizer : sqlRecognizers) { + Assertions.assertEquals(sqlRecognizer.getClass().getName(), MySQLUpdateRecognizer.class.getName()); + } + //test for mysql update and deleted + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("update t set a = a where id =1;update t set a = c where id = 1;delete from t where id =1", JdbcConstants.MYSQL); + }); + //test for mysql select + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("select * from d where id = 1; select * from t where id = 2", JdbcConstants.MYSQL); + }); + + //test for mysql select for update + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("select * from t for update; select * from t where id = 2", JdbcConstants.MYSQL); + }); + + //test for oracle insert + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);insert into t(id) values (2)", JdbcConstants.ORACLE); + }); + + //test for oracle delete and deleted + sql = "delete from t where id =1 ; delete from t where id = 2"; + sqlRecognizers = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + for (SQLRecognizer sqlRecognizer : sqlRecognizers) { + Assertions.assertEquals(sqlRecognizer.getClass().getName(), OracleDeleteRecognizer.class.getName()); + } + + //test for oracle update + sql = "update t set a = b where id =1 ;update t set a = c where id = 1;"; + sqlRecognizers = SQLVisitorFactory.get(sql, JdbcConstants.ORACLE); + for (SQLRecognizer sqlRecognizer : sqlRecognizers) { + Assertions.assertEquals(sqlRecognizer.getClass().getName(), OracleUpdateRecognizer.class.getName()); + } + + //test for oracle select + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("select * from b ; select * from t where id = 2", JdbcConstants.ORACLE); + }); + + //test for oracle select for update + //test for mysql select for update + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("select * from t for update; select * from t where id = 2", JdbcConstants.ORACLE); + }); + + //test for oracle insert and update + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);update t set a = t;", JdbcConstants.ORACLE); + }); + //test for oracle insert and deleted + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + SQLVisitorFactory.get("insert into t(id) values (1);delete from t where id = 1", JdbcConstants.ORACLE); + }); + } + + @Test + public void testSqlRecognizerLoading() { + List recognizers = SQLVisitorFactory.get("update t1 set name = 'test' where id = '1'", JdbcConstants.MYSQL); + Assertions.assertNotNull(recognizers); + Assertions.assertEquals(recognizers.size(), 1); + SQLRecognizer recognizer = recognizers.get(0); + Assertions.assertEquals(SQLType.UPDATE, recognizer.getSQLType()); + Assertions.assertEquals("t1", recognizer.getTableName()); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleDeleteRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleDeleteRecognizerTest.java new file mode 100644 index 0000000..7d3d12c --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleDeleteRecognizerTest.java @@ -0,0 +1,196 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.oracle; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleArgumentExpr; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.oracle.OracleDeleteRecognizer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class OracleDeleteRecognizerTest { + + private static final String DB_TYPE = "oracle"; + + @Test + public void testGetSqlType() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleDeleteRecognizer recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.DELETE); + } + + @Test + public void testGetTableAlias() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleDeleteRecognizer recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleDeleteRecognizer recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleDeleteRecognizer recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for normal sql + Assertions.assertEquals("id = ?", whereCondition); + + sql = "delete from t where id in (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for sql with in + Assertions.assertEquals("id IN (?)", whereCondition); + + sql = "delete from t where id between ? and ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + ArrayList idParam2 = new ArrayList<>(); + idParam.add(2); + Map result = new HashMap(); + result.put(1, idParam); + result.put(2, idParam2); + return result; + } + }, new ArrayList<>()); + //test for sql with in + Assertions.assertEquals("id BETWEEN ? AND ?", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OracleDeleteRecognizer(s, deleteAst).getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return new HashMap<>(); + } + }, new ArrayList<>()); + }); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleDeleteRecognizer recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = 1"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for normal sql + Assertions.assertEquals("id = 1", whereCondition); + + sql = "delete from t where id in (1)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for sql with in + Assertions.assertEquals("id IN (1)", whereCondition); + + sql = "delete from t where id between 1 and 2"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + //test for sql with in + Assertions.assertEquals("id BETWEEN 1 AND 2", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (1)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OracleDeleteRecognizer(s, deleteAst).getWhereCondition(); + }); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleInsertRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleInsertRecognizerTest.java new file mode 100644 index 0000000..5afeb54 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleInsertRecognizerTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.oracle; + +import java.util.Collections; +import java.util.List; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryDoubleExpr; +import io.seata.sqlparser.SQLParsingException; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.oracle.OracleInsertRecognizer; +import io.seata.sqlparser.struct.NotPlaceholderExpr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class OracleInsertRecognizerTest { + + private static final String DB_TYPE = "oracle"; + + @Test + public void testGetSqlType() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleInsertRecognizer recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.INSERT); + } + + @Test + public void testGetTableAlias() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleInsertRecognizer recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleInsertRecognizer recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetInsertColumns() { + + //test for no column + String sql = "insert into t values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleInsertRecognizer recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + List insertColumns = recognizer.getInsertColumns(); + Assertions.assertNull(insertColumns); + + //test for normal + sql = "insert into t(a) values (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + insertColumns = recognizer.getInsertColumns(); + Assertions.assertEquals(1, insertColumns.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getColumns().add(new OracleBinaryDoubleExpr()); + + OracleInsertRecognizer oracleInsertRecognizer = new OracleInsertRecognizer(s, sqlInsertStatement); + oracleInsertRecognizer.getInsertColumns(); + }); + } + + @Test + public void testGetInsertRows() { + final int pkIndex = 0; + //test for null value + String sql = "insert into t(id, no, name, age, time) values (id_seq.nextval, null, 'a', ?, now())"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleInsertRecognizer recognizer = new OracleInsertRecognizer(sql, asts.get(0)); + List> insertRows = recognizer.getInsertRows(Collections.singletonList(pkIndex)); + Assertions.assertEquals(1, insertRows.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getValuesList().get(0).getValues().set(pkIndex, new OracleBinaryDoubleExpr()); + + OracleInsertRecognizer oracleInsertRecognizer = new OracleInsertRecognizer(s, sqlInsertStatement); + oracleInsertRecognizer.getInsertRows(Collections.singletonList(pkIndex)); + }); + } + + @Test + public void testNotPlaceholder_giveValidPkIndex() { + String sql = "insert into test(create_time) values(sysdate)"; + List sqlStatements = SQLUtils.parseStatements(sql, DB_TYPE);; + + OracleInsertRecognizer oracle = new OracleInsertRecognizer(sql, sqlStatements.get(0)); + List> insertRows = oracle.getInsertRows(Collections.singletonList(-1)); + Assertions.assertTrue(insertRows.get(0).get(0) instanceof NotPlaceholderExpr); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleSelectForUpdateRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleSelectForUpdateRecognizerTest.java new file mode 100644 index 0000000..cd85c5b --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleSelectForUpdateRecognizerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.oracle; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLParsingException; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.oracle.OracleSelectForUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author will + */ +public class OracleSelectForUpdateRecognizerTest { + + private static final String DB_TYPE = "oracle"; + + @Test + public void testGetSqlType() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleSelectForUpdateRecognizer recognizer = new OracleSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.SELECT_FOR_UPDATE); + } + + + @Test + public void testGetWhereCondition_0() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleSelectForUpdateRecognizer recognizer = new OracleSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleSelectForUpdateRecognizer recognizer = new OracleSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + + //test for select was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t for update"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.setSelect(null); + new OracleSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + + //test for query was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.getSelect().setQuery(null); + new OracleSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + } + + @Test + public void testGetTableAlias() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleSelectForUpdateRecognizer recognizer = new OracleSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleSelectForUpdateRecognizer recognizer = new OracleSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleUpdateRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleUpdateRecognizerTest.java new file mode 100644 index 0000000..62eec43 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/oracle/OracleUpdateRecognizerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.oracle; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLParsingException; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.oracle.OracleUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author will + */ +public class OracleUpdateRecognizerTest { + + private static final String DB_TYPE = "oracle"; + + @Test + public void testGetSqlType() { + String sql = "update t set n = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.UPDATE); + } + + @Test + public void testGetUpdateColumns() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + List updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + // test with alias + sql = "update t set a.a = ?, a.b = ?, a.c = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + //test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = a"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setColumn(new OracleCursorExpr()); + } + OracleUpdateRecognizer oracleUpdateRecognizer = new OracleUpdateRecognizer(s, sqlUpdateStatement); + oracleUpdateRecognizer.getUpdateColumns(); + }); + } + + @Test + public void testGetUpdateValues() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + List updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with values + sql = "update t set a = 1, b = 2, c = 3"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = ?"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement)sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setValue(new OracleCursorExpr()); + } + OracleUpdateRecognizer oracleUpdateRecognizer = new OracleUpdateRecognizer(s, sqlUpdateStatement); + oracleUpdateRecognizer.getUpdateValues(); + }); + } + + @Test + public void testGetWhereCondition_0() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OracleUpdateRecognizer recognizer = new OracleUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlDeleteRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlDeleteRecognizerTest.java new file mode 100644 index 0000000..2fddbcd --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlDeleteRecognizerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.postgresql; + +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLDeleteRecognizer; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; + +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PostgresqlDeleteRecognizerTest { + + private static final String DB_TYPE = "postgresql"; + + @Test + public void testGetSqlType() { + String sql = "delete from t where id = ?"; + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLDeleteRecognizer recognizer = (SQLDeleteRecognizer) sqlRecognizers.get(0); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.DELETE); + } + + @Test + public void testGetTableAlias() { + String sql = "delete from t where id = ?"; + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLDeleteRecognizer recognizer = (SQLDeleteRecognizer) sqlRecognizers.get(0); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLDeleteRecognizer recognizer = (SQLDeleteRecognizer) sqlRecognizers.get(0); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "delete from t"; + + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLDeleteRecognizer recognizer = (SQLDeleteRecognizer) sqlRecognizers.get(0); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + //test for no condition + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "delete from t"; + + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLDeleteRecognizer recognizer = (SQLDeleteRecognizer) sqlRecognizers.get(0); + String whereCondition = recognizer.getWhereCondition(); + + //test for no condition + Assertions.assertEquals("", whereCondition); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlInsertRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlInsertRecognizerTest.java new file mode 100644 index 0000000..905f2de --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlInsertRecognizerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.postgresql; + +import io.seata.sqlparser.SQLInsertRecognizer; +import io.seata.sqlparser.SQLParsingException; +import io.seata.sqlparser.SQLRecognizer; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.druid.postgresql.PostgresqlInsertRecognizer; + +import java.util.Collections; +import java.util.List; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PostgresqlInsertRecognizerTest { + + private static final String DB_TYPE = "postgresql"; + + @Test + public void testGetSqlType() { + String sql = "insert into t(id) values (?)"; + + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizers.get(0); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.INSERT); + } + + @Test + public void testGetTableAlias() { + String sql = "insert into t(id) values (?)"; + + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizers.get(0); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "insert into t(id) values (?)"; + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizers.get(0); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetInsertColumns() { + + //test for no column + String sql = "insert into t values (?)"; + + List sqlRecognizers = SQLVisitorFactory.get(sql, DB_TYPE); + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) sqlRecognizers.get(0); + List insertColumns = recognizer.getInsertColumns(); + Assertions.assertNull(insertColumns); + + //test for normal + sql = "insert into t(a) values (?)"; + + recognizer = (SQLInsertRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + insertColumns = recognizer.getInsertColumns(); + Assertions.assertEquals(1, insertColumns.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) sqlStatements.get(0); + sqlInsertStatement.getColumns().add(new SQLBetweenExpr()); + + PostgresqlInsertRecognizer postgresqlInsertRecognizer = new PostgresqlInsertRecognizer(s, sqlInsertStatement); + postgresqlInsertRecognizer.getInsertColumns(); + }); + } + + @Test + public void testGetInsertRows() { + final int pkIndex = 0; + //test for null value + String sql = "insert into t(id, no, name, age, time) values (nextval('id_seq'), null, 'a', ?, now())"; + + SQLInsertRecognizer recognizer = (SQLInsertRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + List> insertRows = recognizer.getInsertRows(Collections.singletonList(pkIndex)); + Assertions.assertTrue(insertRows.size() == 1); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement) sqlStatements.get(0); + sqlInsertStatement.getValuesList().get(0).getValues().set(pkIndex, new SQLBetweenExpr()); + + PostgresqlInsertRecognizer postgresqlInsertRecognizer = new PostgresqlInsertRecognizer(s, sqlInsertStatement); + postgresqlInsertRecognizer.getInsertRows(Collections.singletonList(pkIndex)); + }); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlSelectForUpdateRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlSelectForUpdateRecognizerTest.java new file mode 100644 index 0000000..11a402f --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlSelectForUpdateRecognizerTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.postgresql; + +import java.util.ArrayList; +import java.util.Map; + +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLSelectRecognizer; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PostgresqlSelectForUpdateRecognizerTest { + + private static final String DB_TYPE = "postgresql"; + + @Test + public void testGetSqlType() { + String sql = "select * from t where id = ? for update"; + + SQLSelectRecognizer recognizer = (SQLSelectRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.SELECT_FOR_UPDATE); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "select * from t for update"; + + SQLSelectRecognizer recognizer = (SQLSelectRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "select * from t for update"; + + SQLSelectRecognizer recognizer = (SQLSelectRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "select * from t where id = ? for update"; + SQLSelectRecognizer recognizer = (SQLSelectRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "select * from t where id = ? for update"; + SQLSelectRecognizer recognizer = (SQLSelectRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlUpdateRecognizerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlUpdateRecognizerTest.java new file mode 100644 index 0000000..d48cada --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/druid/postgresql/PostgresqlUpdateRecognizerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.druid.postgresql; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import io.seata.rm.datasource.sql.SQLVisitorFactory; +import io.seata.sqlparser.ParametersHolder; +import io.seata.sqlparser.SQLParsingException; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.SQLUpdateRecognizer; +import io.seata.sqlparser.druid.postgresql.PostgresqlUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PostgresqlUpdateRecognizerTest { + + private static final String DB_TYPE = "postgresql"; + + @Test + public void testGetSqlType() { + String sql = "update t set n = ?"; + + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.UPDATE); + } + + @Test + public void testGetUpdateColumns() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + List updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + // test with alias + sql = "update t set a.a = ?, a.b = ?, a.c = ?"; + recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + //test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = a"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setColumn(new SQLBetweenExpr()); + } + PostgresqlUpdateRecognizer postgresqlUpdateRecognizer = new PostgresqlUpdateRecognizer(s, sqlUpdateStatement); + postgresqlUpdateRecognizer.getUpdateColumns(); + }); + } + + @Test + public void testGetUpdateValues() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + List updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with values + sql = "update t set a = 1, b = 2, c = 3"; + recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = ?"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setValue(new SQLBetweenExpr()); + } + PostgresqlUpdateRecognizer postgresqlUpdateRecognizer = new PostgresqlUpdateRecognizer(s, sqlUpdateStatement); + postgresqlUpdateRecognizer.getUpdateValues(); + }); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "update t set a = 1"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "update t set a = 1"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "update t set a = ?, b = ?, c = ?"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "update t set a = ?, b = ?, c = ?"; + SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) SQLVisitorFactory.get(sql, DB_TYPE).get(0); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/ColumnMetaTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/ColumnMetaTest.java new file mode 100644 index 0000000..a47c183 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/ColumnMetaTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class ColumnMetaTest { + + @Test + public void testColumnMeta() { + ColumnMeta columnMeta = new ColumnMeta(); + Assertions.assertNotNull(columnMeta.toString()); + Assertions.assertEquals(columnMeta, new ColumnMeta()); + columnMeta.setIsAutoincrement("Yes"); + Assertions.assertTrue(columnMeta.isAutoincrement()); + Assertions.assertEquals(columnMeta, columnMeta); + Assertions.assertEquals(columnMeta.hashCode(), columnMeta.hashCode()); + Assertions.assertNotEquals(columnMeta, ""); + + ColumnMeta other = new ColumnMeta(); + other.setTableCat(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setTableSchemaName(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setTableName(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setColumnName(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setDataType(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setDataTypeName(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setColumnSize(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setDecimalDigits(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setNumPrecRadix(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setNullAble(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setRemarks(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setColumnDef(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setSqlDataType(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setSqlDatetimeSub(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setCharOctetLength(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setOrdinalPosition(1); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setIsNullAble(""); + Assertions.assertNotEquals(columnMeta, other); + + other = new ColumnMeta(); + other.setIsAutoincrement(""); + Assertions.assertNotEquals(columnMeta, other); + + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexMetaTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexMetaTest.java new file mode 100644 index 0000000..2b3eb99 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexMetaTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class IndexMetaTest { + + @Test + public void testIndexMeta() { + IndexMeta indexMeta = new IndexMeta(); + indexMeta.setValues(Lists.newArrayList()); + Assertions.assertNotNull(indexMeta.toString()); + Assertions.assertEquals(indexMeta, indexMeta); + Assertions.assertEquals(indexMeta.hashCode(), indexMeta.hashCode()); + Assertions.assertNotEquals(indexMeta, ""); + + IndexMeta other = new IndexMeta(); + other.setValues(Lists.newArrayList(new ColumnMeta())); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setNonUnique(true); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setIndexQualifier(""); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setIndexName(""); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setType((short)1); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + indexMeta.setIndextype(IndexType.PRIMARY); + other.setIndextype(IndexType.NORMAL); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setAscOrDesc(""); + //prevent npe and make the unit test go equals ascOrDesc + other.setIndextype(IndexType.NORMAL); + indexMeta.setIndextype(IndexType.NORMAL); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setOrdinalPosition(1); + //prevent npe and make the unit test go equals ordinal position + other.setIndextype(IndexType.NORMAL); + indexMeta.setIndextype(IndexType.NORMAL); + Assertions.assertNotEquals(indexMeta, other); + + other = new IndexMeta(); + other.setIndextype(IndexType.NORMAL); + indexMeta.setIndextype(IndexType.NORMAL); + Assertions.assertEquals(indexMeta, other); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexTypeTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexTypeTest.java new file mode 100644 index 0000000..840d3a4 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/IndexTypeTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class IndexTypeTest { + + @Test + public void testIndexType() { + Assertions.assertNotNull(IndexType.valueOf(1)); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + IndexType.valueOf(4); + }); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java new file mode 100644 index 0000000..0118fe6 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaCacheFactoryTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import com.alibaba.druid.util.JdbcConstants; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache; +import io.seata.rm.datasource.sql.struct.cache.OracleTableMetaCache; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author guoyao + */ +public class TableMetaCacheFactoryTest { + + private static final String NOT_EXIST_SQL_TYPE = "not_exist_sql_type"; + + @Test + public void getTableMetaCache() { + Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL) instanceof MysqlTableMetaCache); + Assertions.assertTrue(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE) instanceof OracleTableMetaCache); + Assertions.assertEquals(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE), TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE)); + Assertions.assertEquals(TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL), TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL)); + Assertions.assertThrows(EnhancedServiceNotFoundException.class, () -> { + TableMetaCacheFactory.getTableMetaCache(NOT_EXIST_SQL_TYPE); + }); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaTest.java new file mode 100644 index 0000000..55ad004 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableMetaTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import com.google.common.collect.Lists; +import io.seata.common.exception.NotSupportYetException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * @author will + */ +public class TableMetaTest { + + private final String COLUMN_ID="id"; + private final String COLUMN_USERCODE="userCode"; + + @Test + public void testTableMeta() { + TableMeta tableMeta = new TableMeta(); + Assertions.assertEquals(tableMeta, tableMeta); + Assertions.assertEquals(tableMeta, new TableMeta()); + Assertions.assertEquals(tableMeta.hashCode(), tableMeta.hashCode()); + Assertions.assertNotEquals(tableMeta, ""); + + TableMeta other = new TableMeta(); + other.setTableName(""); + Assertions.assertNotEquals(tableMeta, other); + + other = new TableMeta(); + other.getAllColumns().put("columnName", new ColumnMeta()); + Assertions.assertNotEquals(tableMeta, other); + + other = new TableMeta(); + other.getAllIndexes().put("indexName", new IndexMeta()); + Assertions.assertNotEquals(tableMeta, other); + } + + @Test + public void testGetColumnMeta() { + TableMeta tableMeta = new TableMeta(); + tableMeta.getAllColumns().put("id", new ColumnMeta()); + tableMeta.getAllColumns().put("name", new ColumnMeta()); + Assertions.assertNull(tableMeta.getColumnMeta("`id`")); + Assertions.assertNotNull(tableMeta.getColumnMeta("name")); + } + + @Test + public void testGetAutoIncreaseColumn() { + TableMeta tableMeta = new TableMeta(); + ColumnMeta id = new ColumnMeta(); + id.setIsAutoincrement("YES"); + tableMeta.getAllColumns().put("id", id); + Assertions.assertNotNull(tableMeta.getAutoIncreaseColumn()); + + tableMeta = new TableMeta(); + tableMeta.getAllColumns().put("name", new ColumnMeta()); + Assertions.assertNull(tableMeta.getAutoIncreaseColumn()); + } + + @Test + public void testGetPrimaryKeyMap() { + TableMeta tableMeta = new TableMeta(); + + IndexMeta primary = new IndexMeta(); + primary.setIndextype(IndexType.PRIMARY); + primary.setValues(Lists.newArrayList(new ColumnMeta())); + + tableMeta.getAllIndexes().put("id", primary); + + Assertions.assertNotNull(tableMeta.getPrimaryKeyMap()); + + + } + + @Test + public void testGetPrimaryKeyOnlyName() { + TableMeta tableMeta = new TableMeta(); + ColumnMeta columnIdMeta=new ColumnMeta(); + columnIdMeta.setColumnName(COLUMN_ID); + IndexMeta primary = new IndexMeta(); + primary.setIndextype(IndexType.PRIMARY); + primary.setValues(Lists.newArrayList(columnIdMeta)); + + ColumnMeta columnUserCodeMeta=new ColumnMeta(); + columnUserCodeMeta.setColumnName(COLUMN_USERCODE); + IndexMeta primary2 = new IndexMeta(); + primary2.setIndextype(IndexType.PRIMARY); + primary2.setValues(Lists.newArrayList(columnUserCodeMeta)); + + tableMeta.getAllIndexes().put(COLUMN_ID, primary); + tableMeta.getAllIndexes().put(COLUMN_USERCODE, primary2); + + List pkColumnName=tableMeta.getPrimaryKeyOnlyName(); + Assertions.assertEquals("id",pkColumnName.get(0)); + Assertions.assertEquals("userCode",pkColumnName.get(1)); + } + + @Test + public void testGetPkName() { + TableMeta tableMeta = new TableMeta(); + IndexMeta primary = new IndexMeta(); + primary.setIndextype(IndexType.PRIMARY); + ColumnMeta columnMeta = new ColumnMeta(); + columnMeta.setColumnName("id"); + primary.setValues(Lists.newArrayList(columnMeta)); + tableMeta.getAllIndexes().put("id", primary); + Assertions.assertEquals("id", tableMeta.getPrimaryKeyOnlyName().get(0)); + } + + @Test + public void testContainsPK() { + TableMeta tableMeta = new TableMeta(); + Assertions.assertFalse(tableMeta.containsPK(null)); + Throwable exception = Assertions.assertThrows(NotSupportYetException.class, () -> tableMeta.containsPK(Lists.newArrayList("id"))); + Assertions.assertEquals(tableMeta.getTableName() + " needs to contain the primary key.", + exception.getMessage()); + IndexMeta primary = new IndexMeta(); + primary.setIndextype(IndexType.PRIMARY); + ColumnMeta columnMeta = new ColumnMeta(); + columnMeta.setColumnName("id"); + primary.setValues(Lists.newArrayList(columnMeta)); + tableMeta.getAllIndexes().put("id", primary); + Assertions.assertTrue(tableMeta.containsPK(Lists.newArrayList("id"))); + Assertions.assertTrue(tableMeta.containsPK(Lists.newArrayList("ID"))); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableRecordsTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableRecordsTest.java new file mode 100644 index 0000000..894f1ee --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/TableRecordsTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct; + +import com.alibaba.druid.mock.MockStatement; +import com.alibaba.druid.mock.MockStatementBase; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.JdbcConstants; + +import com.google.common.collect.Lists; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.mock.MockDriver; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +/** + * the table records test + * @author will + */ +public class TableRecordsTest { + + private static Object[][] columnMetas = + new Object[][] { + new Object[] {"", "", "table_records_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_records_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + new Object[] {"", "", "table_records_test", "information", Types.BLOB, "BLOB", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + new Object[] {"", "", "table_records_test", "description", Types.CLOB, "CLOB", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + + private static Object[][] indexMetas = + new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + private static List returnValueColumnLabels = Lists.newArrayList("id", "name", "information", "description"); + + private static Object[][] returnValue = + new Object[][] { + new Object[] {1, "Tom", "hello", "world"}, + new Object[] {2, "Jack", "hello", "world"}, + }; + + @BeforeEach + public void initBeforeEach() { + } + + @Test + public void testTableRecords() { + + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + TableRecords tableRecords = new TableRecords(new TableMeta()); + tableRecords.setTableMeta(new TableMeta()); + }); + + TableRecords tableRecords = new TableRecords(new TableMeta()); + Assertions.assertEquals(0, tableRecords.size()); + } + + @Test + public void testPkRow() throws SQLException { + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL).getTableMeta(proxy.getPlainConnection(), + "table_records_test", proxy.getResourceId()); + + ResultSet resultSet = mockDriver.executeQuery(mockStatement, "select * from table_records_test"); + + TableRecords tableRecords = TableRecords.buildRecords(tableMeta, resultSet); + + Assertions.assertEquals(returnValue.length, tableRecords.pkRows().size()); + } + + @Test + public void testBuildRecords() throws SQLException { + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + MockStatementBase mockStatement = new MockStatement(dataSource.getConnection().getConnection()); + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL).getTableMeta(proxy.getPlainConnection(), + "table_records_test", proxy.getResourceId()); + + ResultSet resultSet = mockDriver.executeQuery(mockStatement, "select * from table_records_test"); + + TableRecords tableRecords = TableRecords.buildRecords(tableMeta, resultSet); + + Assertions.assertNotNull(tableRecords); + } + + @Test + public void testEmpty() { + TableRecords.EmptyTableRecords emptyTableRecords = new TableRecords.EmptyTableRecords(); + Assertions.assertEquals(0, emptyTableRecords.size()); + + TableRecords empty = TableRecords.empty(new TableMeta()); + + Assertions.assertEquals(0, empty.size()); + Assertions.assertEquals(0, empty.getRows().size()); + Assertions.assertEquals(0, empty.pkRows().size()); + Assertions.assertThrows(UnsupportedOperationException.class, () -> empty.add(new Row())); + Assertions.assertThrows(UnsupportedOperationException.class, empty::getTableMeta); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCacheTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCacheTest.java new file mode 100644 index 0000000..375a8aa --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/MysqlTableMetaCacheTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.JdbcConstants; +import io.seata.common.exception.ShouldNeverHappenException; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.IndexMeta; +import io.seata.rm.datasource.sql.struct.IndexType; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCache; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collections; + +/** + * The table meta fetch test. + * + * @author hanwen created at 2019-02-01 + */ +public class MysqlTableMetaCacheTest { + + private static Object[][] columnMetas = + new Object[][] { + new Object[] {"", "", "mt1", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "mt1", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", + "NO"}, + new Object[] {"", "", "mt1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 3, "YES", + "NO"}, + new Object[] {"", "", "mt1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 4, "YES", + "NO"} + }; + + private static Object[][] indexMetas = + new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 0, "A", 34}, + new Object[] {"name1", "name1", false, "", 3, 1, "A", 34}, + new Object[] {"name2", "name2", true, "", 3, 2, "A", 34}, + }; + + @Test + public void testTableMeta() { + TableMetaCache tableMetaCache = getTableMetaCache(); + Assertions.assertNotNull(tableMetaCache); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + tableMetaCache.getTableMeta(null, null, null); + }); + } + + private TableMetaCache getTableMetaCache() { + return TableMetaCacheFactory.getTableMetaCache(JdbcConstants.MYSQL); + } + + /** + * The table meta fetch test. + */ + @Test + public void getTableMetaTest_0() throws SQLException { + + MockDriver mockDriver = new MockDriver(columnMetas, indexMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + TableMeta tableMeta = getTableMetaCache().getTableMeta(proxy.getPlainConnection(), "mt1", proxy.getResourceId()); + + Assertions.assertEquals("mt1", tableMeta.getTableName()); + Assertions.assertEquals("id", tableMeta.getPrimaryKeyOnlyName().get(0)); + + Assertions.assertEquals("id", tableMeta.getColumnMeta("id").getColumnName()); + Assertions.assertEquals("id", tableMeta.getAutoIncreaseColumn().getColumnName()); + Assertions.assertEquals(1, tableMeta.getPrimaryKeyMap().size()); + Assertions.assertEquals(Collections.singletonList("id"), tableMeta.getPrimaryKeyOnlyName()); + + Assertions.assertEquals(columnMetas.length, tableMeta.getAllColumns().size()); + + assertColumnMetaEquals(columnMetas[0], tableMeta.getAllColumns().get("id")); + assertColumnMetaEquals(columnMetas[1], tableMeta.getAllColumns().get("name1")); + assertColumnMetaEquals(columnMetas[2], tableMeta.getAllColumns().get("name2")); + assertColumnMetaEquals(columnMetas[3], tableMeta.getAllColumns().get("name3")); + + Assertions.assertEquals(indexMetas.length, tableMeta.getAllIndexes().size()); + + assertIndexMetaEquals(indexMetas[0], tableMeta.getAllIndexes().get("PRIMARY")); + Assertions.assertEquals(IndexType.PRIMARY, tableMeta.getAllIndexes().get("PRIMARY").getIndextype()); + assertIndexMetaEquals(indexMetas[1], tableMeta.getAllIndexes().get("name1")); + Assertions.assertEquals(IndexType.UNIQUE, tableMeta.getAllIndexes().get("name1").getIndextype()); + + indexMetas = + new Object[][] { + }; + mockDriver.setMockIndexMetasReturnValue(indexMetas); + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + getTableMetaCache().getTableMeta(proxy.getPlainConnection(), "mt2", proxy.getResourceId()); + }); + + mockDriver.setMockColumnsMetasReturnValue(null); + Assertions.assertThrows(ShouldNeverHappenException.class, () -> { + getTableMetaCache().getTableMeta(proxy.getPlainConnection(), "mt2", proxy.getResourceId()); + }); + + } + + @Test + public void refreshTest_0() throws SQLException { + MockDriver mockDriver = new MockDriver(columnMetas, indexMetas); + + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setUrl("jdbc:mock:xxx"); + druidDataSource.setDriver(mockDriver); + + DataSourceProxy dataSourceProxy = new DataSourceProxy(druidDataSource); + + TableMeta tableMeta = getTableMetaCache().getTableMeta(dataSourceProxy.getPlainConnection(), "t1", + dataSourceProxy.getResourceId()); + //change the columns meta + columnMetas = + new Object[][] { + new Object[] {"", "", "mt1", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "mt1", "name1", Types.VARCHAR, "VARCHAR", 65, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", + "NO"}, + new Object[] {"", "", "mt1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 3, "YES", + "NO"}, + new Object[] {"", "", "mt1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 4, "YES", + "NO"} + }; + mockDriver.setMockColumnsMetasReturnValue(columnMetas); + getTableMetaCache().refresh(dataSourceProxy.getPlainConnection(), dataSourceProxy.getResourceId()); + } + + private void assertColumnMetaEquals(Object[] expected, ColumnMeta actual) { + Assertions.assertEquals(expected[0], actual.getTableCat()); + Assertions.assertEquals(expected[3], actual.getColumnName()); + Assertions.assertEquals(expected[4], actual.getDataType()); + Assertions.assertEquals(expected[5], actual.getDataTypeName()); + Assertions.assertEquals(expected[6], actual.getColumnSize()); + Assertions.assertEquals(expected[7], actual.getDecimalDigits()); + Assertions.assertEquals(expected[8], actual.getNumPrecRadix()); + Assertions.assertEquals(expected[9], actual.getNullAble()); + Assertions.assertEquals(expected[10], actual.getRemarks()); + Assertions.assertEquals(expected[11], actual.getColumnDef()); + Assertions.assertEquals(expected[12], actual.getSqlDataType()); + Assertions.assertEquals(expected[13], actual.getSqlDatetimeSub()); + Assertions.assertEquals(expected[14], actual.getCharOctetLength()); + Assertions.assertEquals(expected[15], actual.getOrdinalPosition()); + Assertions.assertEquals(expected[16], actual.getIsNullAble()); + Assertions.assertEquals(expected[17], actual.getIsAutoincrement()); + } + + private void assertIndexMetaEquals(Object[] expected, IndexMeta actual) { + Assertions.assertEquals(expected[0], actual.getIndexName()); + Assertions.assertEquals(expected[3], actual.getIndexQualifier()); + Assertions.assertEquals(expected[4], (int)actual.getType()); + Assertions.assertEquals(expected[5], actual.getOrdinalPosition()); + Assertions.assertEquals(expected[6], actual.getAscOrDesc()); + Assertions.assertEquals(expected[7], actual.getCardinality()); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java new file mode 100644 index 0000000..3abd6ac --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/OracleTableMetaCacheTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.SQLException; +import java.sql.Types; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.JdbcConstants; + +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCache; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will.zjw + */ +public class OracleTableMetaCacheTest { + + private static Object[][] columnMetas = + new Object[][] { + new Object[] {"", "", "ot1", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "ot1", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", + "NO"}, + new Object[] {"", "", "ot1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 3, "YES", + "NO"}, + new Object[] {"", "", "ot1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 4, "YES", + "NO"} + }; + + private static Object[][] indexMetas = + new Object[][] { + new Object[] {"id", "id", false, "", 3, 0, "A", 34}, + new Object[] {"name1", "name1", false, "", 3, 1, "A", 34}, + new Object[] {"name2", "name2", true, "", 3, 2, "A", 34}, + }; + + private static Object[][] pkMetas = + new Object[][] { + new Object[] {"id"} + }; + + @Test + public void getTableMetaTest() throws SQLException { + MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, pkMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + TableMetaCache tableMetaCache = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.ORACLE); + + TableMeta tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.ot1", proxy.getResourceId()); + + Assertions.assertNotNull(tableMeta); + + tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.\"ot1\"", proxy.getResourceId()); + + Assertions.assertNotNull(tableMeta); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCacheTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCacheTest.java new file mode 100644 index 0000000..a36cafe --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/sql/struct/cache/PostgresqlTableMetaCacheTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.sql.struct.cache; + +import java.sql.SQLException; +import java.sql.Types; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.util.JdbcConstants; + +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableMetaCache; +import io.seata.rm.datasource.sql.struct.TableMetaCacheFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will.zjw + */ +public class PostgresqlTableMetaCacheTest { + + private static Object[][] columnMetas = + new Object[][] { + new Object[] {"", "", "pt1", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "pt1", "name1", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", + "NO"}, + new Object[] {"", "", "pt1", "name2", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 3, "YES", + "NO"}, + new Object[] {"", "", "pt1", "name3", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 4, "YES", + "NO"} + }; + + private static Object[][] indexMetas = + new Object[][] { + new Object[] {"id", "id", false, "", 3, 0, "A", 34}, + new Object[] {"name1", "name1", false, "", 3, 1, "A", 34}, + new Object[] {"name2", "name2", true, "", 3, 2, "A", 34}, + }; + + private static Object[][] pkMetas = + new Object[][] { + new Object[] {"id"} + }; + + @Test + public void getTableMetaTest() throws SQLException { + MockDriver mockDriver = new MockDriver(columnMetas, indexMetas, pkMetas); + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + DataSourceProxy proxy = new DataSourceProxy(dataSource); + + TableMetaCache tableMetaCache = TableMetaCacheFactory.getTableMetaCache(JdbcConstants.POSTGRESQL); + + TableMeta tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "pt1", proxy.getResourceId()); + + Assertions.assertNotNull(tableMeta); + + tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.pt1", proxy.getResourceId()); + + Assertions.assertNotNull(tableMeta); + + tableMeta = tableMetaCache.getTableMeta(proxy.getPlainConnection(), "t.\"pt1\"", proxy.getResourceId()); + + Assertions.assertNotNull(tableMeta); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/AbstractUndoExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/AbstractUndoExecutorTest.java new file mode 100644 index 0000000..d668885 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/AbstractUndoExecutorTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.rm.datasource.SqlGenerateUtils; +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.SQLException; +import java.util.*; + +/** + * @author Geng Zhang + */ +public class AbstractUndoExecutorTest extends BaseH2Test { + + @Test + public void dataValidationUpdate() throws SQLException { + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + + TableRecords beforeImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + execSQL("update table_name set name = 'xxx' where id in (12345, 12346);"); + + TableRecords afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + TestUndoExecutor spy = new TestUndoExecutor(sqlUndoLog, false); + + // case1: normal case before:aaa -> after:xxx -> current:xxx + Assertions.assertTrue(spy.dataValidationAndGoOn(connection)); + + // case2: dirty data before:aaa -> after:xxx -> current:yyy + execSQL("update table_name set name = 'yyy' where id in (12345, 12346);"); + try { + spy.dataValidationAndGoOn(connection); + Assertions.fail(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof SQLException); + } + + // case 3: before == current before:aaa -> after:xxx -> current:aaa + execSQL("update table_name set name = 'aaa' where id in (12345, 12346);"); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + + // case 4: before == after before:aaa -> after:aaa + afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + sqlUndoLog.setAfterImage(afterImage); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + } + + @Test + public void dataValidationInsert() throws SQLException { + TableRecords beforeImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + + TableRecords afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.INSERT); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + TestUndoExecutor spy = new TestUndoExecutor(sqlUndoLog, false); + + // case1: normal case before:0 -> after:2 -> current:2 + Assertions.assertTrue(spy.dataValidationAndGoOn(connection)); + + // case2: dirty data before:0 -> after:2 -> current:2' + execSQL("update table_name set name = 'yyy' where id in (12345, 12346);"); + try { + Assertions.assertTrue(spy.dataValidationAndGoOn(connection)); + Assertions.fail(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof SQLException); + } + + // case3: before == current before:0 -> after:2 -> current:0 + execSQL("delete from table_name where id in (12345, 12346);"); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + + // case 4: before == after before:0 -> after:0 + afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + sqlUndoLog.setAfterImage(afterImage); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + } + + @Test + public void dataValidationDelete() throws SQLException { + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + + TableRecords beforeImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + execSQL("delete from table_name where id in (12345, 12346);"); + + TableRecords afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.INSERT); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + TestUndoExecutor spy = new TestUndoExecutor(sqlUndoLog, true); + + // case1: normal case before:2 -> after:0 -> current:0 + Assertions.assertTrue(spy.dataValidationAndGoOn(connection)); + + // case2: dirty data before:2 -> after:0 -> current:1 + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + try { + Assertions.assertTrue(spy.dataValidationAndGoOn(connection)); + Assertions.fail(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof SQLException); + } + + // case3: before == current before:2 -> after:0 -> current:2 + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + + // case 4: before == after before:2 -> after:2 + afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + sqlUndoLog.setAfterImage(afterImage); + Assertions.assertFalse(spy.dataValidationAndGoOn(connection)); + } + + @Test + public void testParsePK() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"id"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("table_name"); + beforeImage.setTableMeta(tableMeta); + + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + Field field01 = addField(row0, "id", 1, "12345"); + Field field02 = addField(row0, "age", 1, "2"); + beforeRows.add(row0); + Row row1 = new Row(); + Field field11 = addField(row1, "id", 1, "12346"); + Field field12 = addField(row1, "age", 1, "2"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(null); + + TestUndoExecutor executor = new TestUndoExecutor(sqlUndoLog, true); + Map> pkValues = executor.parsePkValues(beforeImage); + Assertions.assertEquals(2, pkValues.get("id").size()); + } + + @Test + public void testBuildWhereConditionByPKs() throws SQLException { + List pkNameList =new ArrayList<>(); + pkNameList.add("id1"); + pkNameList.add("id2"); + + Map> pkRowValues = new HashMap<>(); + List pkId1Values= new ArrayList<>(); + pkId1Values.add(new Field()); + pkId1Values.add(new Field()); + pkId1Values.add(new Field()); + List pkId2Values= new ArrayList<>(); + pkId2Values.add(new Field()); + pkId2Values.add(new Field()); + pkId2Values.add(new Field()); + pkRowValues.put("id1",pkId1Values); + pkRowValues.put("id2",pkId2Values); + + String sql=SqlGenerateUtils.buildWhereConditionByPKs(pkNameList,pkRowValues.get("id1").size(),"mysql"); + Assertions.assertEquals("(id1,id2) in ( (?,?),(?,?),(?,?) )",sql); + } + + @Test + public void testBuildWhereConditionByPK() throws SQLException { + List pkNameList =new ArrayList<>(); + pkNameList.add("id1"); + + Map> pkRowValues = new HashMap<>(); + List pkId1Values= new ArrayList<>(); + pkId1Values.add(new Field()); + List pkId2Values= new ArrayList<>(); + pkId2Values.add(new Field()); + pkRowValues.put("id1",pkId1Values); + + String sql=SqlGenerateUtils.buildWhereConditionByPKs(pkNameList,pkRowValues.get("id1").size(),"mysql"); + Assertions.assertEquals("(id1) in ( (?) )",sql); + } +} + +class TestUndoExecutor extends AbstractUndoExecutor { + private boolean isDelete; + public TestUndoExecutor(SQLUndoLog sqlUndoLog, boolean isDelete) { + super(sqlUndoLog); + this.isDelete = isDelete; + } + + @Override + protected String buildUndoSQL() { + return null; + } + + @Override + protected TableRecords getUndoRows() { + return isDelete ? sqlUndoLog.getBeforeImage() : sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseExecutorTest.java new file mode 100644 index 0000000..e5ecfde --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseExecutorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; + +/** + * @author Geng Zhang + */ +public abstract class BaseExecutorTest { + + protected static Field addField(Row row, String name, int type, Object value) { + Field field = new Field(name, type, value); + if (name.equalsIgnoreCase("id")) { + field.setKeyType(KeyType.PRIMARY_KEY); + } + row.add(field); + return field; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseH2Test.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseH2Test.java new file mode 100644 index 0000000..9da87a7 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseH2Test.java @@ -0,0 +1,131 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.common.util.IOUtil; +import io.seata.rm.datasource.sql.struct.ColumnMeta; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.commons.dbcp2.BasicDataSource; +import org.h2.store.fs.FileUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mockito; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.Arrays; + + +/** + * @author Geng Zhang + */ +public abstract class BaseH2Test { + + static BasicDataSource dataSource = null; + + static Connection connection = null; + + static TableMeta tableMeta = null; + + @BeforeAll + public static void start() throws SQLException { + dataSource = new BasicDataSource(); + dataSource.setDriverClassName("org.h2.Driver"); + dataSource.setUrl("jdbc:h2:./db_store/test_undo"); + dataSource.setUsername("sa"); + dataSource.setPassword(""); + + connection = dataSource.getConnection(); + + tableMeta = mockTableMeta(); + } + + @AfterAll + public static void stop() { + IOUtil.close(connection); + if (dataSource != null) { + try { + dataSource.close(); + } catch (SQLException e) { + } + } + + FileUtils.deleteRecursive("db_store", true); + } + + @BeforeEach + private void prepareTable() { + execSQL("DROP TABLE IF EXISTS table_name"); + execSQL("CREATE TABLE table_name ( `id` int(8), `name` varchar(64), PRIMARY KEY (`id`))"); + } + + protected static void execSQL(String sql) { + Statement s = null; + try { + s = connection.createStatement(); + s.execute(sql); + } catch (Exception e) { + e.printStackTrace(); + } finally { + IOUtil.close(s); + } + } + + protected static TableRecords execQuery(TableMeta tableMeta, String sql) throws SQLException { + Statement s = null; + ResultSet set = null; + try { + s = connection.createStatement(); + set = s.executeQuery(sql); + return TableRecords.buildRecords(tableMeta, set); + } finally { + IOUtil.close(set, s); + } + } + + protected static TableMeta mockTableMeta() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getEscapePkNameList("h2")).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + ColumnMeta meta0 = Mockito.mock(ColumnMeta.class); + Mockito.when(meta0.getDataType()).thenReturn(Types.INTEGER); + Mockito.when(meta0.getColumnName()).thenReturn("ID"); + Mockito.when(tableMeta.getColumnMeta("ID")).thenReturn(meta0); + ColumnMeta meta1 = Mockito.mock(ColumnMeta.class); + Mockito.when(meta1.getDataType()).thenReturn(Types.VARCHAR); + Mockito.when(meta1.getColumnName()).thenReturn("NAME"); + Mockito.when(tableMeta.getColumnMeta("NAME")).thenReturn(meta1); + return tableMeta; + } + + protected static Field addField(Row row, String name, int type, Object value) { + Field field = new Field(name, type, value); + if (name.equalsIgnoreCase("id")) { + field.setKeyType(KeyType.PRIMARY_KEY); + } + row.add(field); + return field; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseUndoLogParserTest.java new file mode 100644 index 0000000..2d89747 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BaseUndoLogParserTest.java @@ -0,0 +1,207 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.sql.JDBCType; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +import io.seata.rm.datasource.DataCompareUtils; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Geng Zhang + */ +public abstract class BaseUndoLogParserTest extends BaseH2Test { + + private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); + + public abstract UndoLogParser getParser(); + + @Test + void testEncodeAndDecode() throws SQLException { + + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + + TableRecords beforeImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + execSQL("update table_name set name = 'xxx' where id in (12345, 12346);"); + TableRecords afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + TableRecords beforeImage2 = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + execSQL("INSERT INTO table_name(id, name) VALUES (12347,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12348,'aaa');"); + TableRecords afterImage2 = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + SQLUndoLog sqlUndoLog00 = new SQLUndoLog(); + sqlUndoLog00.setSqlType(SQLType.UPDATE); + sqlUndoLog00.setTableMeta(tableMeta); + sqlUndoLog00.setTableName("table_name"); + sqlUndoLog00.setBeforeImage(beforeImage); + sqlUndoLog00.setAfterImage(afterImage); + + SQLUndoLog sqlUndoLog01 = new SQLUndoLog(); + sqlUndoLog01.setSqlType(SQLType.UPDATE); + sqlUndoLog01.setTableMeta(tableMeta); + sqlUndoLog01.setTableName("table_name"); + sqlUndoLog01.setBeforeImage(beforeImage2); + sqlUndoLog01.setAfterImage(afterImage2); + + BranchUndoLog originLog = new BranchUndoLog(); + originLog.setBranchId(123456L); + originLog.setXid("xiddddddddddd"); + List logList = new ArrayList<>(); + logList.add(sqlUndoLog00); + logList.add(sqlUndoLog01); + originLog.setSqlUndoLogs(logList); + + // start test + byte[] bs = getParser().encode(originLog); + + Assertions.assertNotNull(bs); + + LOGGER.info("data size:{}", bs.length); + + BranchUndoLog dstLog = getParser().decode(bs); + + Assertions.assertEquals(originLog.getBranchId(), dstLog.getBranchId()); + Assertions.assertEquals(originLog.getXid(), dstLog.getXid()); + Assertions.assertEquals(originLog.getSqlUndoLogs().size(), dstLog.getSqlUndoLogs().size()); + List logList2 = dstLog.getSqlUndoLogs(); + SQLUndoLog sqlUndoLog10 = logList2.get(0); + SQLUndoLog sqlUndoLog11 = logList2.get(1); + Assertions.assertEquals(sqlUndoLog00.getSqlType(), sqlUndoLog10.getSqlType()); + Assertions.assertEquals(sqlUndoLog00.getTableName(), sqlUndoLog10.getTableName()); + Assertions.assertTrue( + DataCompareUtils.isRecordsEquals(sqlUndoLog00.getBeforeImage(), sqlUndoLog10.getBeforeImage()).getResult()); + Assertions.assertTrue( + DataCompareUtils.isRecordsEquals(sqlUndoLog00.getAfterImage(), sqlUndoLog10.getAfterImage()).getResult()); + Assertions.assertEquals(sqlUndoLog01.getSqlType(), sqlUndoLog11.getSqlType()); + Assertions.assertEquals(sqlUndoLog01.getTableName(), sqlUndoLog11.getTableName()); + Assertions.assertTrue( + DataCompareUtils.isRecordsEquals(sqlUndoLog01.getBeforeImage(), sqlUndoLog11.getBeforeImage()).getResult()); + Assertions.assertTrue( + DataCompareUtils.isRecordsEquals(sqlUndoLog01.getAfterImage(), sqlUndoLog11.getAfterImage()).getResult()); + } + + @Test + void testPerformance() throws SQLException { + + execSQL("INSERT INTO table_name(id, name) VALUES (12345,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12346,'aaa');"); + + TableRecords beforeImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + execSQL("update table_name set name = 'xxx' where id in (12345, 12346);"); + TableRecords afterImage = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + TableRecords beforeImage2 = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + execSQL("INSERT INTO table_name(id, name) VALUES (12347,'aaa');"); + execSQL("INSERT INTO table_name(id, name) VALUES (12348,'aaa');"); + TableRecords afterImage2 = execQuery(tableMeta, "SELECT * FROM table_name WHERE id IN (12345, 12346);"); + + SQLUndoLog sqlUndoLog00 = new SQLUndoLog(); + sqlUndoLog00.setSqlType(SQLType.UPDATE); + sqlUndoLog00.setTableMeta(tableMeta); + sqlUndoLog00.setTableName("table_name"); + sqlUndoLog00.setBeforeImage(beforeImage); + sqlUndoLog00.setAfterImage(afterImage); + + SQLUndoLog sqlUndoLog01 = new SQLUndoLog(); + sqlUndoLog01.setSqlType(SQLType.UPDATE); + sqlUndoLog01.setTableMeta(tableMeta); + sqlUndoLog01.setTableName("table_name"); + sqlUndoLog01.setBeforeImage(beforeImage2); + sqlUndoLog01.setAfterImage(afterImage2); + + BranchUndoLog originLog = new BranchUndoLog(); + originLog.setBranchId(123456L); + originLog.setXid("xiddddddddddd"); + List logList = new ArrayList<>(); + logList.add(sqlUndoLog00); + logList.add(sqlUndoLog01); + originLog.setSqlUndoLogs(logList); + + long start = System.currentTimeMillis(); + for (int i = 0; i < 100000; i++) { + byte[] bs = getParser().encode(originLog); + Assertions.assertNotNull(bs); + BranchUndoLog dstLog = getParser().decode(bs); + Assertions.assertEquals(originLog.getBranchId(), dstLog.getBranchId()); + } + long end = System.currentTimeMillis(); + LOGGER.info("elapsed time {} ms.", (end - start)); + } + + @Test + void testDecodeDefaultContent() { + byte[] defaultContent = getParser().getDefaultContent(); + + BranchUndoLog branchUndoLog = getParser().decode(defaultContent); + Assertions.assertNotNull(branchUndoLog); + Assertions.assertNull(branchUndoLog.getXid()); + Assertions.assertEquals(0L, branchUndoLog.getBranchId()); + Assertions.assertNull(branchUndoLog.getSqlUndoLogs()); + } + + /** + * will check kryo、jackson、fastjson、protostuff timestamp encode and decode + */ + @Test + public void testTimestampEncodeAndDecode() { + + BranchUndoLog branchUndoLog = new BranchUndoLog(); + branchUndoLog.setXid("192.168.0.1:8091:123456"); + branchUndoLog.setBranchId(123457); + List sqlUndoLogs = new ArrayList<>(); + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setBeforeImage(TableRecords.empty(new TableMeta())); + Row row = new Row(); + Field field = new Field(); + field.setName("gmt_create"); + field.setKeyType(KeyType.PRIMARY_KEY); + field.setType(JDBCType.TIMESTAMP.getVendorTypeNumber()); + Timestamp timestampEncode = new Timestamp(Integer.MAX_VALUE + 1L); + timestampEncode.setNanos(999999); + field.setValue(timestampEncode); + row.add(field); + TableRecords afterRecords = new TableRecords(); + List rows = new ArrayList<>(); + rows.add(row); + afterRecords.setRows(rows); + afterRecords.setTableMeta(new TableMeta()); + afterRecords.setTableName("test"); + sqlUndoLog.setAfterImage(afterRecords); + sqlUndoLogs.add(sqlUndoLog); + branchUndoLog.setSqlUndoLogs(sqlUndoLogs); + byte[] encode = getParser().encode(branchUndoLog); + BranchUndoLog decodeBranchLog = getParser().decode(encode); + Timestamp timestampDecode = (Timestamp)(decodeBranchLog.getSqlUndoLogs().get(0).getAfterImage().getRows().get(0) + .getFields().get(0).getValue()); + Assertions.assertEquals(timestampEncode, timestampDecode); + + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BranchUndoLogTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BranchUndoLogTest.java new file mode 100644 index 0000000..df2010b --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/BranchUndoLogTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.sql.Types; +import java.util.ArrayList; + +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type Branch undo log test. + */ +public class BranchUndoLogTest { + + /** + * Test encode undo log. + */ + @Test + public void testEncodeUndoLog() { + BranchUndoLog branchUndoLog = new BranchUndoLog(); + branchUndoLog.setBranchId(641789253L); + branchUndoLog.setXid("xid:xxx"); + + ArrayList items = new ArrayList<>(); + SQLUndoLog item = new SQLUndoLog(); + item.setSqlType(SQLType.UPDATE); + + TableMeta tableMeta = new TableMeta(); + tableMeta.setTableName("product"); + + TableRecords beforeImage = new TableRecords(tableMeta); + Row rowb = new Row(); + rowb.add(new Field("id", Types.INTEGER, 1)); + rowb.add(new Field("name", Types.VARCHAR, "SEATA")); + rowb.add(new Field("since", Types.VARCHAR, "2014")); + beforeImage.add(rowb); + item.setBeforeImage(beforeImage); + + TableRecords afterImage = new TableRecords(tableMeta); + Row rowa = new Row(); + rowa.add(new Field("id", Types.INTEGER, 1)); + rowa.add(new Field("name", Types.VARCHAR, "SEATA_IO")); + rowa.add(new Field("since", Types.VARCHAR, "2014")); + afterImage.add(rowa); + item.setAfterImage(afterImage); + + items.add(item); + + branchUndoLog.setSqlUndoLogs(items); + + byte[] bs = UndoLogParserFactory.getInstance().encode(branchUndoLog); + + BranchUndoLog decodeObj = UndoLogParserFactory.getInstance().decode(bs); + Assertions.assertEquals(decodeObj.getBranchId(), branchUndoLog.getBranchId()); + + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/KeywordCheckerFactoryTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/KeywordCheckerFactoryTest.java new file mode 100644 index 0000000..d49619d --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/KeywordCheckerFactoryTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.common.loader.EnhancedServiceNotFoundException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class KeywordCheckerFactoryTest { + + @Test + public void testKeywordCheckerFacotry() { + KeywordCheckerFactory keywordCheckerFactory = new KeywordCheckerFactory(); + Assertions.assertNotNull(keywordCheckerFactory); + + Assertions.assertThrows(EnhancedServiceNotFoundException.class, () -> KeywordCheckerFactory.getKeywordChecker("unknow")); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoExecutorTest.java new file mode 100644 index 0000000..2946e92 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoExecutorTest.java @@ -0,0 +1,1104 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.*; +import java.util.concurrent.Executor; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; + +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * The type Undo executor test. + */ +public class UndoExecutorTest { + + /** + * Test field. + */ + @Test + public void testField() { + Field f = new Field(); + f.setName("name"); + f.setValue("x"); + f.setType(Types.VARCHAR); + f.setKeyType(KeyType.PRIMARY_KEY); + + String s = JSON.toJSONString(f, SerializerFeature.WriteDateUseDateFormat); + + System.out.println(s); + + Field fd = JSON.parseObject(s, Field.class); + System.out.println(fd.getKeyType()); + } + + /** + * Test update. + */ + @Test + public void testUpdate() throws SQLException { + SQLUndoLog SQLUndoLog = new SQLUndoLog(); + SQLUndoLog.setTableName("my_test_table"); + SQLUndoLog.setSqlType(SQLType.UPDATE); + + TableRecords beforeImage = new TableRecords(new MockTableMeta("product", "id")); + + Row beforeRow = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("id"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + beforeRow.add(pkField); + + Field name = new Field(); + name.setName("name"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + beforeRow.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + beforeRow.add(since); + + beforeImage.add(beforeRow); + + TableRecords afterImage = new TableRecords(new MockTableMeta("product", "id")); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("id"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(213); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("name"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + afterImage.add(afterRow); + + SQLUndoLog.setBeforeImage(beforeImage); + SQLUndoLog.setAfterImage(afterImage); + + AbstractUndoExecutor executor = UndoExecutorFactory.getUndoExecutor(JdbcConstants.MYSQL, SQLUndoLog); + MockConnection connection = new MockConnection(); + AbstractUndoExecutor spy = Mockito.spy(executor); + // skip data validation + Mockito.doReturn(true).when(spy).dataValidationAndGoOn(connection); + Mockito.doReturn(JdbcConstants.MYSQL).when(spy).getDbType(connection); + spy.executeOn(connection); + } + + /** + * Test insert. + */ + @Test + public void testInsert() throws SQLException { + SQLUndoLog SQLUndoLog = new SQLUndoLog(); + SQLUndoLog.setTableName("my_test_table"); + SQLUndoLog.setSqlType(SQLType.INSERT); + + TableRecords beforeImage = TableRecords.empty(new MockTableMeta("product", "id")); + + TableRecords afterImage = new TableRecords(new MockTableMeta("product", "id")); + + Row afterRow1 = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("id"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + afterRow1.add(pkField); + + Field name = new Field(); + name.setName("name"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + afterRow1.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + afterRow1.add(since); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("id"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(214); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("name"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + afterImage.add(afterRow1); + afterImage.add(afterRow); + + SQLUndoLog.setBeforeImage(beforeImage); + SQLUndoLog.setAfterImage(afterImage); + + AbstractUndoExecutor executor = UndoExecutorFactory.getUndoExecutor(JdbcConstants.MYSQL, SQLUndoLog); + MockConnection connection = new MockConnection(); + AbstractUndoExecutor spy = Mockito.spy(executor); + // skip data validation + Mockito.doReturn(true).when(spy).dataValidationAndGoOn(connection); + Mockito.doReturn(JdbcConstants.MYSQL).when(spy).getDbType(connection); + spy.executeOn(connection); + } + + /** + * Test delete. + */ + @Test + public void testDelete() throws SQLException { + SQLUndoLog SQLUndoLog = new SQLUndoLog(); + SQLUndoLog.setTableName("my_test_table"); + SQLUndoLog.setSqlType(SQLType.DELETE); + + TableRecords afterImage = TableRecords.empty(new MockTableMeta("product", "id")); + + TableRecords beforeImage = new TableRecords(new MockTableMeta("product", "id")); + + Row afterRow1 = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("id"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + afterRow1.add(pkField); + + Field name = new Field(); + name.setName("name"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + afterRow1.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + afterRow1.add(since); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("id"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(214); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("name"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + beforeImage.add(afterRow1); + beforeImage.add(afterRow); + + SQLUndoLog.setAfterImage(afterImage); + SQLUndoLog.setBeforeImage(beforeImage); + + AbstractUndoExecutor executor = UndoExecutorFactory.getUndoExecutor(JdbcConstants.MYSQL, SQLUndoLog); + MockConnection connection = new MockConnection(); + AbstractUndoExecutor spy = Mockito.spy(executor); + // skip data validation + Mockito.doReturn(true).when(spy).dataValidationAndGoOn(connection); + Mockito.doReturn(JdbcConstants.MYSQL).when(spy).getDbType(connection); + spy.executeOn(connection); + } + + /** + * The type Mock table meta. + */ + public static class MockTableMeta extends TableMeta { + + private String mockPK; + + /** + * Instantiates a new Mock table meta. + * + * @param tableName the table name + * @param pkName the pk name + */ + public MockTableMeta(String tableName, String pkName) { + setTableName(tableName); + this.mockPK = pkName; + + } + + @Override + public String getTableName() { + return super.getTableName(); + } + + @Override + public List getPrimaryKeyOnlyName(){ + return Arrays.asList(new String[]{mockPK}); + } + } + + /** + * The type Mock connection. + */ + public static class MockConnection implements Connection { + + @Override + public Statement createStatement() throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + return new PreparedStatement() { + @Override + public ResultSet executeQuery() throws SQLException { + return null; + } + + @Override + public int executeUpdate() throws SQLException { + return 0; + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + + } + + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + + } + + @Override + public void clearParameters() throws SQLException { + + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + + } + + @Override + public boolean execute() throws SQLException { + return false; + } + + @Override + public void addBatch() throws SQLException { + + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { + + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return null; + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return null; + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { + + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) + throws SQLException { + + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { + + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + return null; + } + + @Override + public int executeUpdate(String sql) throws SQLException { + return 0; + } + + @Override + public void close() throws SQLException { + + } + + @Override + public int getMaxFieldSize() throws SQLException { + return 0; + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + + } + + @Override + public int getMaxRows() throws SQLException { + return 0; + } + + @Override + public void setMaxRows(int max) throws SQLException { + + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + + } + + @Override + public int getQueryTimeout() throws SQLException { + return 0; + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + + } + + @Override + public void cancel() throws SQLException { + + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return null; + } + + @Override + public void clearWarnings() throws SQLException { + + } + + @Override + public void setCursorName(String name) throws SQLException { + + } + + @Override + public boolean execute(String sql) throws SQLException { + return false; + } + + @Override + public ResultSet getResultSet() throws SQLException { + return null; + } + + @Override + public int getUpdateCount() throws SQLException { + return 0; + } + + @Override + public boolean getMoreResults() throws SQLException { + return false; + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + + } + + @Override + public int getFetchDirection() throws SQLException { + return 0; + } + + @Override + public void setFetchSize(int rows) throws SQLException { + + } + + @Override + public int getFetchSize() throws SQLException { + return 0; + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return 0; + } + + @Override + public int getResultSetType() throws SQLException { + return 0; + } + + @Override + public void addBatch(String sql) throws SQLException { + + } + + @Override + public void clearBatch() throws SQLException { + + } + + @Override + public int[] executeBatch() throws SQLException { + return new int[0]; + } + + @Override + public Connection getConnection() throws SQLException { + return null; + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return false; + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return null; + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + return 0; + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + return 0; + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + return 0; + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + return false; + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + return false; + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + return false; + } + + @Override + public int getResultSetHoldability() throws SQLException { + return 0; + } + + @Override + public boolean isClosed() throws SQLException { + return false; + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + + } + + @Override + public boolean isPoolable() throws SQLException { + return false; + } + + @Override + public void closeOnCompletion() throws SQLException { + + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return false; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + }; + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + return null; + } + + @Override + public String nativeSQL(String sql) throws SQLException { + return null; + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + + } + + @Override + public boolean getAutoCommit() throws SQLException { + return false; + } + + @Override + public void commit() throws SQLException { + + } + + @Override + public void rollback() throws SQLException { + + } + + @Override + public void close() throws SQLException { + + } + + @Override + public boolean isClosed() throws SQLException { + return false; + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return null; + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + + } + + @Override + public boolean isReadOnly() throws SQLException { + return false; + } + + @Override + public void setCatalog(String catalog) throws SQLException { + + } + + @Override + public String getCatalog() throws SQLException { + return null; + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + + } + + @Override + public int getTransactionIsolation() throws SQLException { + return 0; + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return null; + } + + @Override + public void clearWarnings() throws SQLException { + + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return null; + } + + @Override + public Map> getTypeMap() throws SQLException { + return null; + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + + } + + @Override + public void setHoldability(int holdability) throws SQLException { + + } + + @Override + public int getHoldability() throws SQLException { + return 0; + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return null; + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return null; + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + return null; + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + return null; + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + return null; + } + + @Override + public Clob createClob() throws SQLException { + return null; + } + + @Override + public Blob createBlob() throws SQLException { + return null; + } + + @Override + public NClob createNClob() throws SQLException { + return null; + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return null; + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return false; + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + + } + + @Override + public String getClientInfo(String name) throws SQLException { + return null; + } + + @Override + public Properties getClientInfo() throws SQLException { + return null; + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return null; + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return null; + } + + @Override + public void setSchema(String schema) throws SQLException { + + } + + @Override + public String getSchema() throws SQLException { + return null; + } + + @Override + public void abort(Executor executor) throws SQLException { + + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + + } + + @Override + public int getNetworkTimeout() throws SQLException { + return 0; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogManagerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogManagerTest.java new file mode 100644 index 0000000..8498485 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogManagerTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author guoyao + */ +public class UndoLogManagerTest { + + + private static final int APPEND_IN_SIZE = 10; + + + private static final String THE_APPEND_IN_SIZE_PARAM_STRING = " (?,?,?,?,?,?,?,?,?,?) "; + + private static final String THE_DOUBLE_APPEND_IN_SIZE_PARAM_STRING = " (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "; + + @Test + public void testBatchDeleteUndoLog() throws Exception { + Set xids = new HashSet<>(); + for (int i = 0;i < APPEND_IN_SIZE;i++){ + xids.add(UUID.randomUUID().toString()); + } + Set branchIds = new HashSet<>(); + for (int i = 0;i < APPEND_IN_SIZE;i++){ + branchIds.add((long) i); + } + Connection connection = mock(Connection.class); + PreparedStatement preparedStatement = mock(PreparedStatement.class); + when(connection.prepareStatement(anyString())).thenReturn(preparedStatement); + UndoLogManagerFactory.getUndoLogManager(JdbcConstants.MYSQL).batchDeleteUndoLog(xids, branchIds, connection); + + //verify + for (int i = 1;i <= APPEND_IN_SIZE;i++){ + verify(preparedStatement).setLong(eq(i),anyLong()); + } + for (int i = APPEND_IN_SIZE + 1;i <= APPEND_IN_SIZE * 2;i++){ + verify(preparedStatement).setString(eq(i),anyString()); + } + verify(preparedStatement).executeUpdate(); + } + + @Test + public void testToBatchDeleteUndoLogSql() { + String expectedSqlString="DELETE FROM undo_log WHERE branch_id IN " + + THE_APPEND_IN_SIZE_PARAM_STRING + + " AND xid IN " + + THE_DOUBLE_APPEND_IN_SIZE_PARAM_STRING; + String batchDeleteUndoLogSql = AbstractUndoLogManager.toBatchDeleteUndoLogSql(APPEND_IN_SIZE * 2, APPEND_IN_SIZE); + System.out.println(batchDeleteUndoLogSql); + assertThat(batchDeleteUndoLogSql).isEqualTo(expectedSqlString); + } + + @Test + public void testAppendInParam() { + StringBuilder sqlBuilder = new StringBuilder(); + AbstractUndoLogManager.appendInParam(APPEND_IN_SIZE, sqlBuilder); + assertThat(sqlBuilder.toString()).isEqualTo(THE_APPEND_IN_SIZE_PARAM_STRING); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserFactoryTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserFactoryTest.java new file mode 100644 index 0000000..6a1c099 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserFactoryTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import io.seata.rm.datasource.undo.parser.JacksonUndoLogParser; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +class UndoLogParserFactoryTest { + + @Test + void getInstance() { + Assertions.assertTrue(UndoLogParserFactory.getInstance() instanceof JacksonUndoLogParser); + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserProviderTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserProviderTest.java new file mode 100644 index 0000000..29eaab7 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/UndoLogParserProviderTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.loader.EnhancedServiceNotFoundException; +import io.seata.rm.datasource.undo.parser.FastjsonUndoLogParser; +import io.seata.rm.datasource.undo.parser.FstUndoLogParser; +import io.seata.rm.datasource.undo.parser.JacksonUndoLogParser; +import io.seata.rm.datasource.undo.parser.KryoUndoLogParser; +import io.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser; + +/** + * @author Geng Zhang + */ +class UndoLogParserProviderTest { + + @Test + void testLoad(){ + UndoLogParser parser = EnhancedServiceLoader.load(UndoLogParser.class, "fastjson"); + Assertions.assertNotNull(parser); + Assertions.assertTrue(parser instanceof FastjsonUndoLogParser); + + parser = EnhancedServiceLoader.load(UndoLogParser.class, "jackson"); + Assertions.assertNotNull(parser); + Assertions.assertTrue(parser instanceof JacksonUndoLogParser); + + parser = EnhancedServiceLoader.load(UndoLogParser.class, "protostuff"); + Assertions.assertNotNull(parser); + Assertions.assertTrue(parser instanceof ProtostuffUndoLogParser); + + parser = EnhancedServiceLoader.load(UndoLogParser.class, "fst"); + Assertions.assertNotNull(parser); + Assertions.assertTrue(parser instanceof FstUndoLogParser); + + parser = EnhancedServiceLoader.load(UndoLogParser.class, "kryo"); + Assertions.assertNotNull(parser); + Assertions.assertTrue(parser instanceof KryoUndoLogParser); + + try { + EnhancedServiceLoader.load(UndoLogParser.class, "adadad"); + Assertions.fail(); + } catch (Exception e) { + Assertions.assertTrue(e instanceof EnhancedServiceNotFoundException); + } + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/h2/keyword/H2KeywordChecker.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/h2/keyword/H2KeywordChecker.java new file mode 100644 index 0000000..d7660aa --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/h2/keyword/H2KeywordChecker.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seata.rm.datasource.undo.h2.keyword; + +import io.seata.common.loader.LoadLevel; +import io.seata.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker; +import io.seata.sqlparser.util.JdbcConstants; + +/** + * @author JerryYin + */ +@LoadLevel(name = JdbcConstants.H2) +public class H2KeywordChecker extends MySQLKeywordChecker { +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutorTest.java new file mode 100644 index 0000000..7866781 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoDeleteExecutorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.when; + +/** + * @author Geng Zhang + */ +public class MySQLUndoDeleteExecutorTest extends BaseExecutorTest { + + private static MySQLUndoDeleteExecutor executor; + + @BeforeAll + public static void init(){ + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"id"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("table_name"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "id", 1, "12345"); + addField(row0, "age", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "id", 1, "12346"); + addField(row1, "age", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("table_name"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "id", 1, "12345"); + addField(row2, "age", 1, "2"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "id", 1, "12346"); + addField(row3, "age", 1, "2"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + executor = new MySQLUndoDeleteExecutor(sqlUndoLog); + } + + @Test + public void buildUndoSQL() { + String sql = executor.buildUndoSQL().toLowerCase(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("insert")); + Assertions.assertTrue(sql.contains("id")); + } + + @Test + public void getUndoRows() { + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutorTest.java new file mode 100644 index 0000000..37a48db --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoInsertExecutorTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Geng Zhang + */ +public class MySQLUndoInsertExecutorTest extends BaseExecutorTest { + + private static MySQLUndoInsertExecutor executor; + + @BeforeAll + public static void init(){ + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"id"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("table_name"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "id", 1, "12345"); + addField(row0, "age", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "id", 1, "12346"); + addField(row1, "age", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("table_name"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "id", 1, "12345"); + addField(row2, "age", 1, "2"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "id", 1, "12346"); + addField(row3, "age", 1, "2"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + executor = new MySQLUndoInsertExecutor(sqlUndoLog); + } + + @Test + public void buildUndoSQL() { + String sql = executor.buildUndoSQL().toLowerCase(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("delete")); + Assertions.assertTrue(sql.contains("id")); + } + + @Test + public void getUndoRows() { + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getAfterImage()); + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManagerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManagerTest.java new file mode 100644 index 0000000..b0741ac --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoLogManagerTest.java @@ -0,0 +1,216 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import com.alibaba.druid.pool.DruidDataSource; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.ConnectionContext; +import io.seata.rm.datasource.ConnectionProxy; +import io.seata.rm.datasource.DataSourceProxy; +import io.seata.rm.datasource.mock.MockDriver; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.AbstractUndoLogManager; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.undo.UndoLogParserFactory; +import io.seata.rm.datasource.undo.parser.JacksonUndoLogParser; +import io.seata.sqlparser.SQLRecognizerFactory; +import io.seata.sqlparser.SQLType; +import io.seata.sqlparser.SqlParserType; +import io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import io.seata.sqlparser.druid.SQLOperateRecognizerHolderFactory; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class MySQLUndoLogManagerTest { + + List returnValueColumnLabels = Lists.newArrayList("log_status"); + Object[][] returnValue = new Object[][] { + new Object[] {1}, + new Object[] {2}, + }; + Object[][] columnMetas = new Object[][] { + new Object[] {"", "", "table_plain_executor_test", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[] {"", "", "table_plain_executor_test", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][] { + new Object[] {"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + + private DruidDataSource dataSource; + + private DataSourceProxy dataSourceProxy; + + private ConnectionProxy connectionProxy; + + private MySQLUndoLogManager undoLogManager; + + private TableMeta tableMeta; + + @BeforeAll + public static void setup(){ + EnhancedServiceLoader.load(SQLOperateRecognizerHolder.class, JdbcConstants.MYSQL, + SQLOperateRecognizerHolderFactory.class.getClassLoader()); + DruidDelegatingSQLRecognizerFactory recognizerFactory = (DruidDelegatingSQLRecognizerFactory) EnhancedServiceLoader + .load(SQLRecognizerFactory.class, SqlParserType.SQL_PARSER_TYPE_DRUID); + } + + @BeforeEach + public void init() throws SQLException { + MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); + dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mock:xxx"); + dataSource.setDriver(mockDriver); + + dataSourceProxy = new DataSourceProxy(dataSource); + connectionProxy = new ConnectionProxy(dataSourceProxy, dataSource.getConnection().getConnection()); + undoLogManager = new MySQLUndoLogManager(); + tableMeta = new TableMeta(); + tableMeta.setTableName("table_plain_executor_test"); + } + + @Test + public void testDeleteUndoLogByLogCreated() throws SQLException { + Assertions.assertEquals(0, undoLogManager.deleteUndoLogByLogCreated(new Date(), 3000, dataSource.getConnection())); + Assertions.assertDoesNotThrow(() -> undoLogManager.deleteUndoLogByLogCreated(new Date(), 3000, connectionProxy)); + } + + @Test + public void testInsertUndoLog() throws SQLException { + Assertions.assertDoesNotThrow(() -> undoLogManager.insertUndoLogWithGlobalFinished("xid", 1L, new JacksonUndoLogParser(), + dataSource.getConnection())); + + Assertions.assertDoesNotThrow(() -> undoLogManager.insertUndoLogWithNormal("xid", 1L, "", new byte[]{}, dataSource.getConnection())); + + Assertions.assertDoesNotThrow(() -> undoLogManager.deleteUndoLogByLogCreated(new Date(), 3000, connectionProxy)); + + } + + @Test + public void testSerializer() { + MySQLUndoLogManager.setCurrentSerializer("jackson"); + Assertions.assertEquals("jackson", MySQLUndoLogManager.getCurrentSerializer()); + MySQLUndoLogManager.removeCurrentSerializer(); + Assertions.assertNull(MySQLUndoLogManager.getCurrentSerializer()); + } + + @Test + public void testDeleteUndoLog() { + Assertions.assertDoesNotThrow(() -> undoLogManager.deleteUndoLog("xid", 1L, dataSource.getConnection())); + + Assertions.assertDoesNotThrow(() -> undoLogManager.deleteUndoLog("xid", 1L, connectionProxy)); + } + + @Test + public void testBatchDeleteUndoLog() { + Assertions.assertDoesNotThrow(() -> undoLogManager.batchDeleteUndoLog(Sets.newHashSet("xid"), Sets.newHashSet(1L), dataSource.getConnection())); + + Assertions.assertDoesNotThrow(() -> undoLogManager.batchDeleteUndoLog(Sets.newHashSet("xid"), Sets.newHashSet(1L), connectionProxy)); + } + + @Test + public void testFlushUndoLogs() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { + connectionProxy.bind("xid"); + ConnectionContext context = connectionProxy.getContext(); + Method method = context.getClass().getDeclaredMethod("setBranchId", Long.class); + method.setAccessible(true); + method.invoke(context, 1L); + + SQLUndoLog undoLogItem = getUndoLogItem(1); + undoLogItem.setTableName("test"); + Method appendUndoItemMethod = context.getClass().getDeclaredMethod("appendUndoItem", SQLUndoLog.class); + appendUndoItemMethod.setAccessible(true); + appendUndoItemMethod.invoke(context, undoLogItem); + + Assertions.assertDoesNotThrow(() -> undoLogManager.flushUndoLogs(connectionProxy)); + } + + @Test + public void testNeedCompress() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + SQLUndoLog smallUndoItem = getUndoLogItem(1); + BranchUndoLog smallBranchUndoLog = new BranchUndoLog(); + smallBranchUndoLog.setBranchId(1L); + smallBranchUndoLog.setXid("test_xid"); + smallBranchUndoLog.setSqlUndoLogs(Collections.singletonList(smallUndoItem)); + UndoLogParser parser = UndoLogParserFactory.getInstance(); + byte[] smallUndoLogContent = parser.encode(smallBranchUndoLog); + + Method method = AbstractUndoLogManager.class.getDeclaredMethod("needCompress", byte[].class); + method.setAccessible(true); + Assertions.assertFalse((Boolean) method.invoke(undoLogManager, smallUndoLogContent)); + + SQLUndoLog hugeUndoItem = getUndoLogItem(10000); + BranchUndoLog hugeBranchUndoLog = new BranchUndoLog(); + hugeBranchUndoLog.setBranchId(2L); + hugeBranchUndoLog.setXid("test_xid1"); + hugeBranchUndoLog.setSqlUndoLogs(Collections.singletonList(hugeUndoItem)); + byte[] hugeUndoLogContent = parser.encode(hugeBranchUndoLog); + Assertions.assertTrue((Boolean) method.invoke(undoLogManager, hugeUndoLogContent)); + } + + @Test + public void testUndo() throws SQLException { + Assertions.assertDoesNotThrow(() -> undoLogManager.undo(dataSourceProxy, "xid", 1L)); + } + + private SQLUndoLog getUndoLogItem(int size) throws NoSuchFieldException, IllegalAccessException { + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setTableName("table_plain_executor_test"); + sqlUndoLog.setSqlType(SQLType.INSERT); + sqlUndoLog.setTableMeta(tableMeta); + + Field rowsField = TableRecords.class.getDeclaredField("rows"); + rowsField.setAccessible(true); + + List rows = new ArrayList<>(size); + for (int i = 0; i < size; i ++) { + Row row = new Row(); + row.add(new io.seata.rm.datasource.sql.struct.Field("id", 1, "value_id_" + i)); + row.add(new io.seata.rm.datasource.sql.struct.Field("name", 1, "value_name_" + i)); + rows.add(row); + } + + sqlUndoLog.setAfterImage(TableRecords.empty(tableMeta)); + TableRecords afterImage = new TableRecords(tableMeta); + rowsField.set(afterImage, rows); + sqlUndoLog.setAfterImage(afterImage); + + return sqlUndoLog; + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutorTest.java new file mode 100644 index 0000000..11c6ffc --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/MySQLUndoUpdateExecutorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql; + +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Geng Zhang + */ +public class MySQLUndoUpdateExecutorTest extends BaseExecutorTest { + + private static MySQLUndoUpdateExecutor executor; + + @BeforeAll + public static void init(){ + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"id"})); + Mockito.when(tableMeta.getTableName()).thenReturn("table_name"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("table_name"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "id", 1, "12345"); + addField(row0, "age", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "id", 1, "12346"); + addField(row1, "age", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("table_name"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "id", 1, "12345"); + addField(row2, "age", 1, "2"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "id", 1, "12346"); + addField(row3, "age", 1, "2"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("table_name"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + executor = new MySQLUndoUpdateExecutor(sqlUndoLog); + } + + @Test + public void buildUndoSQL() { + String sql = executor.buildUndoSQL().toLowerCase(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("update")); + Assertions.assertTrue(sql.contains("id")); + Assertions.assertTrue(sql.contains("age")); + } + + @Test + public void getUndoRows() { + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } + +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java new file mode 100644 index 0000000..72fb6d0 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/mysql/keyword/MySQLKeywordCheckerTest.java @@ -0,0 +1,318 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.mysql.keyword; + +import java.sql.SQLException; +import java.sql.Types; + +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.rm.datasource.undo.KeywordCheckerFactory; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.mysql.MySQLUndoDeleteExecutor; +import io.seata.rm.datasource.undo.mysql.MySQLUndoInsertExecutor; +import io.seata.rm.datasource.undo.mysql.MySQLUndoUpdateExecutor; + +import io.seata.rm.datasource.undo.UndoExecutorTest; +import io.seata.sqlparser.SQLType; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The type My sql keyword checker test. + * + * @author Wu + */ +public class MySQLKeywordCheckerTest { + + /** + * Test check + */ + @Test + public void testCheck() { + KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.MYSQL); + Assertions.assertTrue(keywordChecker.check("desc")); + } + + /** + * Test keyword check with UPDATE case + */ + @Test + public void testUpdateKeywordCheck() { + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setTableName("`lock`"); + sqlUndoLog.setSqlType(SQLType.UPDATE); + + TableRecords beforeImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "key")); + + Row beforeRow = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("`key`"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + beforeRow.add(pkField); + + Field name = new Field(); + name.setName("`desc`"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + beforeRow.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + beforeRow.add(since); + + beforeImage.add(beforeRow); + + TableRecords afterImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "key")); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("`key`"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(214); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("`desc`"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + afterImage.add(afterRow); + + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + MySQLUndoUpdateExecutorExtension mySQLUndoUpdateExecutor = new MySQLUndoUpdateExecutorExtension(sqlUndoLog); + + Assertions.assertEquals("UPDATE `lock` SET `desc` = ?, since = ? WHERE `key` = ?", + mySQLUndoUpdateExecutor.getSql().trim()); + + } + + private static class MySQLUndoUpdateExecutorExtension extends MySQLUndoUpdateExecutor { + /** + * Instantiates a new My sql undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoUpdateExecutorExtension(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * Gets sql. + * + * @return the sql + */ + public String getSql() { + return super.buildUndoSQL(); + } + } + + /** + * Test keyword check with INSERT case + */ + @Test + public void testInsertKeywordCheck() { + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setTableName("`lock`"); + sqlUndoLog.setSqlType(SQLType.INSERT); + + TableRecords beforeImage = TableRecords.empty(new UndoExecutorTest.MockTableMeta("product", "key")); + + TableRecords afterImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "key")); + + Row afterRow1 = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("`key`"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + afterRow1.add(pkField); + + Field name = new Field(); + name.setName("`desc`"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + afterRow1.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + afterRow1.add(since); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("`key`"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(214); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("`desc`"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + afterImage.add(afterRow1); + afterImage.add(afterRow); + + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + MySQLUndoInsertExecutorExtension mySQLUndoInsertExecutor = new MySQLUndoInsertExecutorExtension(sqlUndoLog); + + Assertions.assertEquals("DELETE FROM `lock` WHERE `key` = ?", mySQLUndoInsertExecutor.getSql().trim()); + + } + + private static class MySQLUndoInsertExecutorExtension extends MySQLUndoInsertExecutor { + /** + * Instantiates a new My sql undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoInsertExecutorExtension(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * Gets sql. + * + * @return the sql + */ + public String getSql() { + return super.buildUndoSQL(); + } + } + + /** + * Test keyword check with DELETE case + */ + @Test + public void testDeleteKeywordCheck() { + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setTableName("`lock`"); + sqlUndoLog.setSqlType(SQLType.DELETE); + + TableRecords afterImage = TableRecords.empty(new UndoExecutorTest.MockTableMeta("product", "key")); + + TableRecords beforeImage = new TableRecords(new UndoExecutorTest.MockTableMeta("product", "key")); + + Row afterRow1 = new Row(); + + Field pkField = new Field(); + pkField.setKeyType(KeyType.PRIMARY_KEY); + pkField.setName("`key`"); + pkField.setType(Types.INTEGER); + pkField.setValue(213); + afterRow1.add(pkField); + + Field name = new Field(); + name.setName("`desc`"); + name.setType(Types.VARCHAR); + name.setValue("SEATA"); + afterRow1.add(name); + + Field since = new Field(); + since.setName("since"); + since.setType(Types.VARCHAR); + since.setValue("2014"); + afterRow1.add(since); + + Row afterRow = new Row(); + + Field pkField1 = new Field(); + pkField1.setKeyType(KeyType.PRIMARY_KEY); + pkField1.setName("`key`"); + pkField1.setType(Types.INTEGER); + pkField1.setValue(214); + afterRow.add(pkField1); + + Field name1 = new Field(); + name1.setName("`desc`"); + name1.setType(Types.VARCHAR); + name1.setValue("GTS"); + afterRow.add(name1); + + Field since1 = new Field(); + since1.setName("since"); + since1.setType(Types.VARCHAR); + since1.setValue("2016"); + afterRow.add(since1); + + beforeImage.add(afterRow1); + beforeImage.add(afterRow); + + sqlUndoLog.setAfterImage(afterImage); + sqlUndoLog.setBeforeImage(beforeImage); + + MySQLUndoDeleteExecutorExtension mySQLUndoDeleteExecutor = new MySQLUndoDeleteExecutorExtension(sqlUndoLog); + + Assertions.assertEquals("INSERT INTO `lock` (`desc`, since, `key`) VALUES (?, ?, ?)", + mySQLUndoDeleteExecutor.getSql()); + + } + + private static class MySQLUndoDeleteExecutorExtension extends MySQLUndoDeleteExecutor { + /** + * Instantiates a new My sql undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public MySQLUndoDeleteExecutorExtension(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * Gets sql. + * + * @return the sql + */ + public String getSql() { + return super.buildUndoSQL(); + } + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutorTest.java new file mode 100644 index 0000000..279a33c --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoDeleteExecutorTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author jsbxyyx + */ +public class OracleUndoDeleteExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQL() { + OracleUndoDeleteExecutor executor = upperCase(); + + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("INSERT")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + OracleUndoDeleteExecutor executor = upperCase(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } + + private OracleUndoDeleteExecutor upperCase() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "2"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "2"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.DELETE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new OracleUndoDeleteExecutor(sqlUndoLog); + } + + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutorTest.java new file mode 100644 index 0000000..74e67da --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutorTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author jsbxyyx + */ +public class OracleUndoInsertExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQL() { + OracleUndoInsertExecutor executor = upperCase(); + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("DELETE")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + OracleUndoInsertExecutor executor = upperCase(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getAfterImage()); + } + + public OracleUndoInsertExecutor upperCase() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "1"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "1"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.INSERT); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new OracleUndoInsertExecutor(sqlUndoLog); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutorTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutorTest.java new file mode 100644 index 0000000..3848669 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/OracleUndoUpdateExecutorTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle; + +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableMeta; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.BaseExecutorTest; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author jsbxyyx + */ +public class OracleUndoUpdateExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQLByUpperCase() { + OracleUndoUpdateExecutor executor = upperCaseSQL(); + + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("UPDATE")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("AGE")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + OracleUndoUpdateExecutor executor = upperCaseSQL(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } + + private OracleUndoUpdateExecutor upperCaseSQL() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "1"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "1"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new OracleUndoUpdateExecutor(sqlUndoLog); + } + +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordCheckerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordCheckerTest.java new file mode 100644 index 0000000..04d505b --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/oracle/keyword/OracleKeywordCheckerTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.oracle.keyword; + +import com.alibaba.druid.util.JdbcConstants; + +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.rm.datasource.undo.KeywordCheckerFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class OracleKeywordCheckerTest { + + @Test + public void testOracleKeywordChecker() { + KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.ORACLE); + Assertions.assertNotNull(keywordChecker); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java new file mode 100644 index 0000000..5e49540 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FastjsonUndoLogParserTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializeConfig; +import com.alibaba.fastjson.serializer.ValueFilter; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.sql.struct.Field; +import io.seata.rm.datasource.sql.struct.KeyType; +import io.seata.rm.datasource.sql.struct.Row; +import io.seata.rm.datasource.sql.struct.TableRecords; +import io.seata.rm.datasource.undo.BaseUndoLogParserTest; +import io.seata.rm.datasource.undo.BranchUndoLog; +import io.seata.rm.datasource.undo.SQLUndoLog; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.sqlparser.SQLType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +public class FastjsonUndoLogParserTest extends BaseUndoLogParserTest { + + FastjsonUndoLogParser parser = (FastjsonUndoLogParser) EnhancedServiceLoader.load(UndoLogParser.class, FastjsonUndoLogParser.NAME); + + @Override + public UndoLogParser getParser() { + return parser; + } + + /** + * disable super testTimestampEncodeAndDecode + */ + @Override + public void testTimestampEncodeAndDecode() { + Timestamp encodeStamp = new Timestamp(System.currentTimeMillis()); + encodeStamp.setNanos(999999); + SerializeConfig.getGlobalInstance().addFilter(Timestamp.class, new TimestampSerializer()); + byte[] encode = JSON.toJSONString(encodeStamp, SerializeConfig.getGlobalInstance()).getBytes(); + } + + @Test + public void testWriteClassName() throws Exception { + TableRecords beforeImage = new TableRecords(); + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("t1"); + List rows = new ArrayList<>(); + Row row = new Row(); + Field field = new Field(); + field.setName("id"); + field.setKeyType(KeyType.PRIMARY_KEY); + field.setType(Types.BIGINT); + field.setValue(Long.valueOf("0")); + row.add(field); + field = new Field(); + field.setName("money"); + field.setType(Types.DECIMAL); + field.setValue(BigDecimal.ONE); + row.add(field); + rows.add(row); + afterImage.setRows(rows); + + SQLUndoLog sqlUndoLog00 = new SQLUndoLog(); + sqlUndoLog00.setSqlType(SQLType.INSERT); + sqlUndoLog00.setTableName("table_name"); + sqlUndoLog00.setBeforeImage(beforeImage); + sqlUndoLog00.setAfterImage(afterImage); + + BranchUndoLog originLog = new BranchUndoLog(); + originLog.setBranchId(123456L); + originLog.setXid("xiddddddddddd"); + List logList = new ArrayList<>(); + logList.add(sqlUndoLog00); + originLog.setSqlUndoLogs(logList); + + // start test + byte[] bs = getParser().encode(originLog); + + String s = new String(bs); + Assertions.assertTrue(s.contains("\"@type\"")); + + BranchUndoLog decode = getParser().decode(s.getBytes()); + Object value1 = decode.getSqlUndoLogs().get(0).getAfterImage().getRows().get(0).getFields().get(0).getValue(); + Object value2 = decode.getSqlUndoLogs().get(0).getAfterImage().getRows().get(0).getFields().get(1).getValue(); + Assertions.assertTrue(value1 instanceof Long); + Assertions.assertTrue(value2 instanceof BigDecimal); + } + + private class TimestampSerializer implements ValueFilter { + + @Override + public Object process(Object object, String name, Object value) { + return null; + } + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FstUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FstUndoLogParserTest.java new file mode 100644 index 0000000..693c6f1 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/FstUndoLogParserTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.undo.BaseUndoLogParserTest; +import io.seata.rm.datasource.undo.UndoLogParser; + +/** + * @author funkye + */ +public class FstUndoLogParserTest extends BaseUndoLogParserTest { + + FstUndoLogParser parser = (FstUndoLogParser)EnhancedServiceLoader.load(UndoLogParser.class, FstUndoLogParser.NAME); + + @Override + public UndoLogParser getParser() { + return parser; + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java new file mode 100644 index 0000000..caa4f9f --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/JacksonUndoLogParserTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import java.io.IOException; +import java.math.BigDecimal; +import java.sql.JDBCType; +import java.sql.SQLException; +import java.sql.Timestamp; + +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.DataCompareUtils; +import io.seata.rm.datasource.undo.BaseUndoLogParserTest; +import io.seata.rm.datasource.undo.UndoLogParser; +import io.seata.rm.datasource.sql.struct.Field; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author Geng Zhang + */ +public class JacksonUndoLogParserTest extends BaseUndoLogParserTest { + + JacksonUndoLogParser parser = (JacksonUndoLogParser) EnhancedServiceLoader.load(UndoLogParser.class, JacksonUndoLogParser.NAME); + + @Test + public void encode() throws NoSuchFieldException, IllegalAccessException, IOException, SQLException { + //get the jackson mapper + java.lang.reflect.Field reflectField = parser.getClass().getDeclaredField("mapper"); + reflectField.setAccessible(true); + ObjectMapper mapper = (ObjectMapper)reflectField.get(parser); + + //bigint type + Field field = new Field("bigint_type", JDBCType.BIGINT.getVendorTypeNumber(), 9223372036854775807L); + byte[] bytes = mapper.writeValueAsBytes(field); + Field sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + + //big decimal type + field = new Field("decimal_type", JDBCType.DECIMAL.getVendorTypeNumber(), new BigDecimal("55555555555555555555.55555555555555555555")); + bytes = mapper.writeValueAsBytes(field); + sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + + //double type + field = new Field("double_type", JDBCType.DOUBLE.getVendorTypeNumber(), 999999.999999999); + bytes = mapper.writeValueAsBytes(field); + sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + + //timestamp type + field = new Field("timestamp_type", JDBCType.TIMESTAMP.getVendorTypeNumber(), Timestamp.valueOf("2019-08-10 10:49:26.926554")); + bytes = mapper.writeValueAsBytes(field); + sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + + //blob type + field = new Field("blob_type", JDBCType.BLOB.getVendorTypeNumber(), new SerialBlob("hello".getBytes())); + bytes = mapper.writeValueAsBytes(field); + sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + + //clob type + field = new Field("clob_type", JDBCType.CLOB.getVendorTypeNumber(), new SerialClob("hello".toCharArray())); + bytes = mapper.writeValueAsBytes(field); + sameField = mapper.readValue(bytes, Field.class); + Assertions.assertTrue(DataCompareUtils.isFieldEquals(field, sameField).getResult()); + } + + @Override + public UndoLogParser getParser() { + return parser; + } +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParserTest.java new file mode 100644 index 0000000..2c85be8 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/KryoUndoLogParserTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.undo.BaseUndoLogParserTest; +import io.seata.rm.datasource.undo.UndoLogParser; + +/** + * @author jsbxyyx + */ +public class KryoUndoLogParserTest extends BaseUndoLogParserTest { + + KryoUndoLogParser parser = (KryoUndoLogParser) EnhancedServiceLoader.load(UndoLogParser.class, KryoUndoLogParser.NAME); + + @Override + public UndoLogParser getParser() { + return parser; + } + + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParserTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParserTest.java new file mode 100644 index 0000000..65b5ab4 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/parser/ProtostuffUndoLogParserTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.parser; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.rm.datasource.undo.BaseUndoLogParserTest; +import io.seata.rm.datasource.undo.UndoLogParser; + +/** + * @author Geng Zhang + */ +class ProtostuffUndoLogParserTest extends BaseUndoLogParserTest { + + ProtostuffUndoLogParser parser = (ProtostuffUndoLogParser) EnhancedServiceLoader.load(UndoLogParser.class, ProtostuffUndoLogParser.NAME); + + @Override + public UndoLogParser getParser() { + return parser; + } + +} \ No newline at end of file diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordCheckerTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordCheckerTest.java new file mode 100644 index 0000000..77d7f2f --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/undo/postgresql/keyword/PostgresqlKeywordCheckerTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.undo.postgresql.keyword; + +import com.alibaba.druid.util.JdbcConstants; + +import io.seata.rm.datasource.undo.KeywordChecker; +import io.seata.rm.datasource.undo.KeywordCheckerFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author will + */ +public class PostgresqlKeywordCheckerTest { + + @Test + public void testOracleKeywordChecker() { + KeywordChecker keywordChecker = KeywordCheckerFactory.getKeywordChecker(JdbcConstants.POSTGRESQL); + Assertions.assertNotNull(keywordChecker); + } + +} diff --git a/rm-datasource/src/test/java/io/seata/rm/datasource/util/JdbcUtilsTest.java b/rm-datasource/src/test/java/io/seata/rm/datasource/util/JdbcUtilsTest.java new file mode 100644 index 0000000..9f2125b --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/datasource/util/JdbcUtilsTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.datasource.util; + +import io.seata.sqlparser.util.DbTypeParser; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class JdbcUtilsTest { + @Test + public void testDbTypeParserLoading() { + DbTypeParser dbTypeParser = JdbcUtils.getDbTypeParser(); + Assertions.assertNotNull(dbTypeParser); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/xa/ConnectionProxyXATest.java b/rm-datasource/src/test/java/io/seata/rm/xa/ConnectionProxyXATest.java new file mode 100644 index 0000000..66eea05 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/xa/ConnectionProxyXATest.java @@ -0,0 +1,213 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.xa; + +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.core.model.ResourceManager; +import io.seata.rm.BaseDataSourceResource; +import io.seata.rm.DefaultResourceManager; +import io.seata.rm.datasource.xa.ConnectionProxyXA; +import io.seata.rm.datasource.xa.StatementProxyXA; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import java.sql.Connection; +import java.sql.Statement; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; + +/** + * Tests for ConnectionProxyXA + * + * @author sharajava + */ +public class ConnectionProxyXATest { + + @Test + public void testInit() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(false); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + + Assertions.assertThrows(IllegalStateException.class, + connectionProxyXA::init, + "Connection[autocommit=false] as default is NOT supported"); + } + + @Test + public void testXABranchCommit() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + + XAResource xaResource = Mockito.mock(XAResource.class); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getXAResource()).thenReturn(xaResource); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.doNothing().when(resourceManager).registerResource(any(Resource.class)); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.XA, resourceManager); + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA.init(); + + connectionProxyXA.setAutoCommit(false); + + // Assert setAutoCommit = false was NEVER invoked on the wrapped connection + Mockito.verify(connection, times(0)).setAutoCommit(false); + // Assert XA start was invoked + Mockito.verify(xaResource).start(any(Xid.class), any(Integer.class)); + + connectionProxyXA.commit(); + + Mockito.verify(xaResource).end(any(Xid.class), any(Integer.class)); + Mockito.verify(xaResource).prepare(any(Xid.class)); + } + + @Test + public void testXABranchRollback() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + + XAResource xaResource = Mockito.mock(XAResource.class); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getXAResource()).thenReturn(xaResource); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.doNothing().when(resourceManager).registerResource(any(Resource.class)); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.XA, resourceManager); + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA.init(); + + connectionProxyXA.setAutoCommit(false); + + // Assert setAutoCommit = false was NEVER invoked on the wrapped connection + Mockito.verify(connection, times(0)).setAutoCommit(false); + + // Assert XA start was invoked + Mockito.verify(xaResource).start(any(Xid.class), any(Integer.class)); + + connectionProxyXA.rollback(); + + Mockito.verify(xaResource).end(any(Xid.class), any(Integer.class)); + + // Not prepared + Mockito.verify(xaResource, times(0)).prepare(any(Xid.class)); + } + + @Test + public void testClose() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + + XAConnection xaConnection = Mockito.mock(XAConnection.class); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + + ConnectionProxyXA connectionProxyXA1 = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA1.init(); + // Kept + connectionProxyXA1.setHeld(true); + // call close on proxy + connectionProxyXA1.close(); + // Assert the original connection was NOT closed + Mockito.verify(connection, times(0)).close(); + + ConnectionProxyXA connectionProxyXA2 = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA2.init(); + // Kept + connectionProxyXA2.setHeld(false); + // call close on proxy + connectionProxyXA2.close(); + // Assert the original connection was ALSO closed + Mockito.verify(connection).close(); + } + + @Test + public void testXACommit() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + + XAResource xaResource = Mockito.mock(XAResource.class); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getXAResource()).thenReturn(xaResource); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA.init(); + + connectionProxyXA.xaCommit("xxx", 123L, null); + + Mockito.verify(xaResource).commit(any(Xid.class), any(Boolean.class)); + Mockito.verify(xaResource, times(0)).rollback(any(Xid.class)); + } + + @Test + public void testXARollback() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + + XAResource xaResource = Mockito.mock(XAResource.class); + + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getXAResource()).thenReturn(xaResource); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA.init(); + + connectionProxyXA.xaRollback("xxx", 123L, null); + + Mockito.verify(xaResource, times(0)).commit(any(Xid.class), any(Boolean.class)); + Mockito.verify(xaResource).rollback(any(Xid.class)); + } + + @Test + public void testCreateStatement() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + Statement statement = connectionProxyXA.createStatement(); + Assertions.assertTrue(statement instanceof StatementProxyXA); + } + + @AfterAll + public static void tearDown(){ + RootContext.unbind(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXANativeTest.java b/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXANativeTest.java new file mode 100644 index 0000000..1fbb697 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXANativeTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.xa; + +import io.seata.rm.datasource.xa.ConnectionProxyXA; +import io.seata.rm.datasource.xa.DataSourceProxyXANative; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +/** + * Tests for DataSourceProxyXANative + * + * @author sharajava + */ +public class DataSourceProxyXANativeTest { + + @Test + public void testGetConnection() throws SQLException { + // Mock + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + DatabaseMetaData metaData = Mockito.mock(DatabaseMetaData.class); + Mockito.when(metaData.getURL()).thenReturn("jdbc:mysql:xxx"); + Mockito.when(connection.getMetaData()).thenReturn(metaData); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getConnection()).thenReturn(connection); + XADataSource xaDataSource = Mockito.mock(XADataSource.class); + Mockito.when(xaDataSource.getXAConnection()).thenReturn(xaConnection); + + DataSourceProxyXANative dataSourceProxyXANative = new DataSourceProxyXANative(xaDataSource); + Connection connFromDataSourceProxyXANative = dataSourceProxyXANative.getConnection(); + + Assertions.assertTrue(connFromDataSourceProxyXANative instanceof ConnectionProxyXA); + XAConnection xaConnectionFromProxy = ((ConnectionProxyXA)connFromDataSourceProxyXANative).getWrappedXAConnection(); + Assertions.assertTrue(xaConnection == xaConnectionFromProxy); + Connection connectionFromProxy = ((ConnectionProxyXA)connFromDataSourceProxyXANative).getWrappedConnection(); + Assertions.assertTrue(connection == connectionFromProxy); + + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXATest.java b/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXATest.java new file mode 100644 index 0000000..f0d83c1 --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/xa/DataSourceProxyXATest.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.xa; + +import com.alibaba.druid.pool.DruidDataSource; +import com.mysql.jdbc.JDBC4MySQLConnection; +import com.mysql.jdbc.jdbc2.optional.JDBC4ConnectionWrapper; +import io.seata.core.context.RootContext; +import io.seata.rm.datasource.mock.MockDataSource; +import io.seata.rm.datasource.xa.ConnectionProxyXA; +import io.seata.rm.datasource.xa.DataSourceProxyXA; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.sql.DataSource; +import javax.sql.PooledConnection; +import javax.sql.XAConnection; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.SQLException; + +import static org.mockito.ArgumentMatchers.any; + +/** + * Tests for DataSourceProxyXA + * + * @author sharajava + */ +public class DataSourceProxyXATest { + + @Test + public void test_constructor() { + DataSource dataSource = new MockDataSource(); + + DataSourceProxyXA dataSourceProxy = new DataSourceProxyXA(dataSource); + Assertions.assertEquals(dataSourceProxy.getTargetDataSource(), dataSource); + + DataSourceProxyXA dataSourceProxy2 = new DataSourceProxyXA(dataSourceProxy); + Assertions.assertEquals(dataSourceProxy2.getTargetDataSource(), dataSource); + } + + @Test + public void testGetConnection() throws SQLException { + // Mock + Driver driver = Mockito.mock(Driver.class); + JDBC4MySQLConnection connection = Mockito.mock(JDBC4MySQLConnection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + DatabaseMetaData metaData = Mockito.mock(DatabaseMetaData.class); + Mockito.when(metaData.getURL()).thenReturn("jdbc:mysql:xxx"); + Mockito.when(connection.getMetaData()).thenReturn(metaData); + Mockito.when(driver.connect(any(), any())).thenReturn(connection); + + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setDriver(driver); + DataSourceProxyXA dataSourceProxyXA = new DataSourceProxyXA(druidDataSource); + Connection connFromDataSourceProxyXA = dataSourceProxyXA.getConnection(); + Assertions.assertFalse(connFromDataSourceProxyXA instanceof ConnectionProxyXA); + RootContext.bind("test"); + connFromDataSourceProxyXA = dataSourceProxyXA.getConnection(); + + Assertions.assertTrue(connFromDataSourceProxyXA instanceof ConnectionProxyXA); + ConnectionProxyXA connectionProxyXA = (ConnectionProxyXA)dataSourceProxyXA.getConnection(); + + Connection wrappedConnection = connectionProxyXA.getWrappedConnection(); + Assertions.assertTrue(wrappedConnection instanceof PooledConnection); + + Connection wrappedPhysicalConn = ((PooledConnection)wrappedConnection).getConnection(); + Assertions.assertTrue(wrappedPhysicalConn == connection); + + XAConnection xaConnection = connectionProxyXA.getWrappedXAConnection(); + Connection connectionInXA = xaConnection.getConnection(); + Assertions.assertTrue(connectionInXA instanceof JDBC4ConnectionWrapper); + } + + @AfterAll + public static void tearDown(){ + RootContext.unbind(); + } +} diff --git a/rm-datasource/src/test/java/io/seata/rm/xa/XAXidBuilderTest.java b/rm-datasource/src/test/java/io/seata/rm/xa/XAXidBuilderTest.java new file mode 100644 index 0000000..e3e9eae --- /dev/null +++ b/rm-datasource/src/test/java/io/seata/rm/xa/XAXidBuilderTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm.xa; + +import io.seata.rm.datasource.xa.XAXid; +import io.seata.rm.datasource.xa.XAXidBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Tests for XAXidBuilder + * + * @author sharajava + */ +public class XAXidBuilderTest { + + @Test + public void testXid() throws Throwable { + long mockBranchId = 1582688600006L; + String mockXid = "127.0.0.1:8091:" + mockBranchId; + XAXid xaXid = XAXidBuilder.build(mockXid, mockBranchId); + + XAXid retrievedXAXid = XAXidBuilder.build(xaXid.getGlobalTransactionId(), xaXid.getBranchQualifier()); + String retrievedXid = retrievedXAXid.getGlobalXid(); + long retrievedBranchId = retrievedXAXid.getBranchId(); + + Assertions.assertEquals(mockXid, retrievedXid); + Assertions.assertEquals(mockBranchId, retrievedBranchId); + + } +} diff --git a/rm-datasource/src/test/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker b/rm-datasource/src/test/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker new file mode 100644 index 0000000..de38969 --- /dev/null +++ b/rm-datasource/src/test/resources/META-INF/services/io.seata.rm.datasource.undo.KeywordChecker @@ -0,0 +1 @@ +io.seata.rm.datasource.undo.h2.keyword.H2KeywordChecker \ No newline at end of file diff --git a/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.SQLRecognizerFactory b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.SQLRecognizerFactory new file mode 100644 index 0000000..f4b2f82 --- /dev/null +++ b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.SQLRecognizerFactory @@ -0,0 +1,17 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory diff --git a/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.druid.SQLOperateRecognizerHolder b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.druid.SQLOperateRecognizerHolder new file mode 100644 index 0000000..e7a870f --- /dev/null +++ b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.druid.SQLOperateRecognizerHolder @@ -0,0 +1,19 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.sqlparser.druid.mysql.MySQLOperateRecognizerHolder +io.seata.sqlparser.druid.oracle.OracleOperateRecognizerHolder +io.seata.sqlparser.druid.postgresql.PostgresqlOperateRecognizerHolder \ No newline at end of file diff --git a/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.util.DbTypeParser b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.util.DbTypeParser new file mode 100644 index 0000000..983f0c3 --- /dev/null +++ b/rm-datasource/src/test/resources/META-INF/services/io.seata.sqlparser.util.DbTypeParser @@ -0,0 +1,17 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.sqlparser.druid.DruidDelegatingDbTypeParser diff --git a/rm/pom.xml b/rm/pom.xml new file mode 100644 index 0000000..64c78ae --- /dev/null +++ b/rm/pom.xml @@ -0,0 +1,43 @@ + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + seata-rm + jar + seata-rm ${project.version} + + + + ${project.groupId} + seata-core + ${project.version} + + + com.google.guava + guava + + + + + diff --git a/rm/src/main/java/io/seata/rm/AbstractRMHandler.java b/rm/src/main/java/io/seata/rm/AbstractRMHandler.java new file mode 100644 index 0000000..a80db88 --- /dev/null +++ b/rm/src/main/java/io/seata/rm/AbstractRMHandler.java @@ -0,0 +1,159 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.exception.AbstractExceptionHandler; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.AbstractMessage; +import io.seata.core.protocol.AbstractResultMessage; +import io.seata.core.protocol.transaction.AbstractTransactionRequestToRM; +import io.seata.core.protocol.transaction.BranchCommitRequest; +import io.seata.core.protocol.transaction.BranchCommitResponse; +import io.seata.core.protocol.transaction.BranchRollbackRequest; +import io.seata.core.protocol.transaction.BranchRollbackResponse; +import io.seata.core.protocol.transaction.RMInboundHandler; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; +import io.seata.core.rpc.RpcContext; +import io.seata.core.rpc.TransactionMessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Abstract RM event handler + * + * @author sharajava + */ +public abstract class AbstractRMHandler extends AbstractExceptionHandler + implements RMInboundHandler, TransactionMessageHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRMHandler.class); + + @Override + public BranchCommitResponse handle(BranchCommitRequest request) { + BranchCommitResponse response = new BranchCommitResponse(); + exceptionHandleTemplate(new AbstractCallback() { + @Override + public void execute(BranchCommitRequest request, BranchCommitResponse response) + throws TransactionException { + doBranchCommit(request, response); + } + }, request, response); + return response; + } + + @Override + public BranchRollbackResponse handle(BranchRollbackRequest request) { + BranchRollbackResponse response = new BranchRollbackResponse(); + exceptionHandleTemplate(new AbstractCallback() { + @Override + public void execute(BranchRollbackRequest request, BranchRollbackResponse response) + throws TransactionException { + doBranchRollback(request, response); + } + }, request, response); + return response; + } + + /** + * delete undo log + * @param request the request + */ + @Override + public void handle(UndoLogDeleteRequest request) { + // https://github.com/seata/seata/issues/2226 + } + + /** + * Do branch commit. + * + * @param request the request + * @param response the response + * @throws TransactionException the transaction exception + */ + protected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response) + throws TransactionException { + String xid = request.getXid(); + long branchId = request.getBranchId(); + String resourceId = request.getResourceId(); + String applicationData = request.getApplicationData(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Branch committing: " + xid + " " + branchId + " " + resourceId + " " + applicationData); + } + BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId, + applicationData); + response.setXid(xid); + response.setBranchId(branchId); + response.setBranchStatus(status); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Branch commit result: " + status); + } + + } + + /** + * Do branch rollback. + * + * @param request the request + * @param response the response + * @throws TransactionException the transaction exception + */ + protected void doBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response) + throws TransactionException { + String xid = request.getXid(); + long branchId = request.getBranchId(); + String resourceId = request.getResourceId(); + String applicationData = request.getApplicationData(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Branch Rollbacking: " + xid + " " + branchId + " " + resourceId); + } + BranchStatus status = getResourceManager().branchRollback(request.getBranchType(), xid, branchId, resourceId, + applicationData); + response.setXid(xid); + response.setBranchId(branchId); + response.setBranchStatus(status); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Branch Rollbacked result: " + status); + } + } + + /** + * get resource manager implement + * + * @return resource manager + */ + protected abstract ResourceManager getResourceManager(); + + @Override + public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) { + if (!(request instanceof AbstractTransactionRequestToRM)) { + throw new IllegalArgumentException(); + } + AbstractTransactionRequestToRM transactionRequest = (AbstractTransactionRequestToRM)request; + transactionRequest.setRMInboundMessageHandler(this); + + return transactionRequest.handle(context); + } + + @Override + public void onResponse(AbstractResultMessage response, RpcContext context) { + LOGGER.info("the rm client received response msg [{}] from tc server.", response.toString()); + } + + public abstract BranchType getBranchType(); +} diff --git a/rm/src/main/java/io/seata/rm/AbstractResourceManager.java b/rm/src/main/java/io/seata/rm/AbstractResourceManager.java new file mode 100644 index 0000000..79b360c --- /dev/null +++ b/rm/src/main/java/io/seata/rm/AbstractResourceManager.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.common.exception.NotSupportYetException; +import io.seata.core.exception.RmTransactionException; +import io.seata.core.exception.TransactionException; +import io.seata.core.exception.TransactionExceptionCode; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.ResultCode; +import io.seata.core.protocol.transaction.BranchRegisterRequest; +import io.seata.core.protocol.transaction.BranchRegisterResponse; +import io.seata.core.protocol.transaction.BranchReportRequest; +import io.seata.core.protocol.transaction.BranchReportResponse; +import io.seata.core.rpc.netty.RmNettyRemotingClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeoutException; + +/** + * abstract ResourceManager + * + * @author zhangsen + */ +public abstract class AbstractResourceManager implements ResourceManager { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractResourceManager.class); + + /** + * registry branch record + * + * @param branchType the branch type + * @param resourceId the resource id + * @param clientId the client id + * @param xid the xid + * @param lockKeys the lock keys + * @return branchId + * @throws TransactionException TransactionException + */ + @Override + public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException { + try { + BranchRegisterRequest request = new BranchRegisterRequest(); + request.setXid(xid); + request.setLockKey(lockKeys); + request.setResourceId(resourceId); + request.setBranchType(branchType); + request.setApplicationData(applicationData); + + BranchRegisterResponse response = (BranchRegisterResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request); + if (response.getResultCode() == ResultCode.Failed) { + throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg())); + } + return response.getBranchId(); + } catch (TimeoutException toe) { + throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe); + } catch (RuntimeException rex) { + throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex); + } + } + + /** + * report branch status + * + * @param branchType the branch type + * @param xid the xid + * @param branchId the branch id + * @param status the status + * @param applicationData the application data + * @throws TransactionException TransactionException + */ + @Override + public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException { + try { + BranchReportRequest request = new BranchReportRequest(); + request.setXid(xid); + request.setBranchId(branchId); + request.setStatus(status); + request.setApplicationData(applicationData); + + BranchReportResponse response = (BranchReportResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request); + if (response.getResultCode() == ResultCode.Failed) { + throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg())); + } + } catch (TimeoutException toe) { + throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe); + } catch (RuntimeException rex) { + throw new RmTransactionException(TransactionExceptionCode.BranchReportFailed, "Runtime", rex); + } + } + + @Override + public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys) throws TransactionException { + return false; + } + + @Override + public void unregisterResource(Resource resource) { + throw new NotSupportYetException("unregister a resource"); + } + + @Override + public void registerResource(Resource resource) { + RmNettyRemotingClient.getInstance().registerResource(resource.getResourceGroupId(), resource.getResourceId()); + } +} diff --git a/rm/src/main/java/io/seata/rm/DefaultRMHandler.java b/rm/src/main/java/io/seata/rm/DefaultRMHandler.java new file mode 100644 index 0000000..6888847 --- /dev/null +++ b/rm/src/main/java/io/seata/rm/DefaultRMHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.transaction.BranchCommitRequest; +import io.seata.core.protocol.transaction.BranchCommitResponse; +import io.seata.core.protocol.transaction.BranchRollbackRequest; +import io.seata.core.protocol.transaction.BranchRollbackResponse; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; +import org.slf4j.MDC; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * the default RM event handler implement, deal with the phase two events + * + * @author zhangsen + */ +public class DefaultRMHandler extends AbstractRMHandler { + + protected static Map allRMHandlersMap = new ConcurrentHashMap<>(); + + protected DefaultRMHandler() { + initRMHandlers(); + } + + protected void initRMHandlers() { + List allRMHandlers = EnhancedServiceLoader.loadAll(AbstractRMHandler.class); + if (CollectionUtils.isNotEmpty(allRMHandlers)) { + for (AbstractRMHandler rmHandler : allRMHandlers) { + allRMHandlersMap.put(rmHandler.getBranchType(), rmHandler); + } + } + } + + @Override + public BranchCommitResponse handle(BranchCommitRequest request) { + MDC.put(RootContext.MDC_KEY_XID, request.getXid()); + MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(request.getBranchId())); + return getRMHandler(request.getBranchType()).handle(request); + } + + @Override + public BranchRollbackResponse handle(BranchRollbackRequest request) { + MDC.put(RootContext.MDC_KEY_XID, request.getXid()); + MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(request.getBranchId())); + return getRMHandler(request.getBranchType()).handle(request); + } + + @Override + public void handle(UndoLogDeleteRequest request) { + getRMHandler(request.getBranchType()).handle(request); + } + + protected AbstractRMHandler getRMHandler(BranchType branchType) { + return allRMHandlersMap.get(branchType); + } + + @Override + protected ResourceManager getResourceManager() { + throw new FrameworkException("DefaultRMHandler isn't a real AbstractRMHandler"); + } + + private static class SingletonHolder { + private static AbstractRMHandler INSTANCE = new DefaultRMHandler(); + } + + /** + * Get resource manager. + * + * @return the resource manager + */ + public static AbstractRMHandler get() { + return DefaultRMHandler.SingletonHolder.INSTANCE; + } + + @Override + public BranchType getBranchType() { + throw new FrameworkException("DefaultRMHandler isn't a real AbstractRMHandler"); + } +} diff --git a/rm/src/main/java/io/seata/rm/DefaultResourceManager.java b/rm/src/main/java/io/seata/rm/DefaultResourceManager.java new file mode 100644 index 0000000..36d23d8 --- /dev/null +++ b/rm/src/main/java/io/seata/rm/DefaultResourceManager.java @@ -0,0 +1,157 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; +import io.seata.core.model.ResourceManager; + +/** + * default resource manager, adapt all resource managers + * + * @author zhangsen + */ +public class DefaultResourceManager implements ResourceManager { + + /** + * all resource managers + */ + protected static Map resourceManagers + = new ConcurrentHashMap<>(); + + private DefaultResourceManager() { + initResourceManagers(); + } + + /** + * Get resource manager. + * + * @return the resource manager + */ + public static DefaultResourceManager get() { + return SingletonHolder.INSTANCE; + } + + /** + * only for mock + * + * @param branchType branchType + * @param rm resource manager + */ + public static void mockResourceManager(BranchType branchType, ResourceManager rm) { + resourceManagers.put(branchType, rm); + } + + protected void initResourceManagers() { + //init all resource managers + List allResourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class); + if (CollectionUtils.isNotEmpty(allResourceManagers)) { + for (ResourceManager rm : allResourceManagers) { + resourceManagers.put(rm.getBranchType(), rm); + } + } + } + + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, + String resourceId, String applicationData) + throws TransactionException { + return getResourceManager(branchType).branchCommit(branchType, xid, branchId, resourceId, applicationData); + } + + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, + String resourceId, String applicationData) + throws TransactionException { + return getResourceManager(branchType).branchRollback(branchType, xid, branchId, resourceId, applicationData); + } + + @Override + public Long branchRegister(BranchType branchType, String resourceId, + String clientId, String xid, String applicationData, String lockKeys) + throws TransactionException { + return getResourceManager(branchType).branchRegister(branchType, resourceId, clientId, xid, applicationData, + lockKeys); + } + + @Override + public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, + String applicationData) throws TransactionException { + getResourceManager(branchType).branchReport(branchType, xid, branchId, status, applicationData); + } + + @Override + public boolean lockQuery(BranchType branchType, String resourceId, + String xid, String lockKeys) throws TransactionException { + return getResourceManager(branchType).lockQuery(branchType, resourceId, xid, lockKeys); + } + + @Override + public void registerResource(Resource resource) { + getResourceManager(resource.getBranchType()).registerResource(resource); + } + + @Override + public void unregisterResource(Resource resource) { + getResourceManager(resource.getBranchType()).unregisterResource(resource); + } + + @Override + public Map getManagedResources() { + Map allResource = new HashMap<>(); + for (ResourceManager rm : resourceManagers.values()) { + Map tempResources = rm.getManagedResources(); + if (tempResources != null) { + allResource.putAll(tempResources); + } + } + return allResource; + } + + /** + * get ResourceManager by Resource Type + * + * @param branchType branch type + * @return resource manager + */ + public ResourceManager getResourceManager(BranchType branchType) { + ResourceManager rm = resourceManagers.get(branchType); + if (rm == null) { + throw new FrameworkException("No ResourceManager for BranchType:" + branchType.name()); + } + return rm; + } + + @Override + public BranchType getBranchType() { + throw new FrameworkException("DefaultResourceManager isn't a real ResourceManager"); + } + + private static class SingletonHolder { + private static DefaultResourceManager INSTANCE = new DefaultResourceManager(); + } + +} diff --git a/rm/src/main/java/io/seata/rm/RMClient.java b/rm/src/main/java/io/seata/rm/RMClient.java new file mode 100644 index 0000000..9660009 --- /dev/null +++ b/rm/src/main/java/io/seata/rm/RMClient.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.rm; + +import io.seata.core.rpc.netty.RmNettyRemotingClient; + +/** + * The Rm client Initiator. + * + * @author slievrly + */ +public class RMClient { + + /** + * Init. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + */ + public static void init(String applicationId, String transactionServiceGroup) { + RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); + rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get()); + rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get()); + rmNettyRemotingClient.init(); + } + +} diff --git a/saga/pom.xml b/saga/pom.xml new file mode 100644 index 0000000..b90c548 --- /dev/null +++ b/saga/pom.xml @@ -0,0 +1,64 @@ + + + + + + io.seata + seata-parent + ${revision} + + 4.0.0 + pom + seata-saga + seata-saga ${project.version} + + + seata-saga-processctrl + seata-saga-statelang + seata-saga-engine + seata-saga-rm + seata-saga-tm + seata-saga-engine-store + + + + + ${project.groupId} + seata-common + ${project.parent.version} + + + + com.alibaba + fastjson + provided + + + + com.fasterxml.jackson.core + jackson-databind + provided + + + + org.codehaus.groovy + groovy-all + + + diff --git a/saga/seata-saga-engine-store/pom.xml b/saga/seata-saga-engine-store/pom.xml new file mode 100644 index 0000000..353c7c6 --- /dev/null +++ b/saga/seata-saga-engine-store/pom.xml @@ -0,0 +1,52 @@ + + + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-engine-store ${project.version} + seata-saga-engine-store + + + + ${project.groupId} + seata-saga-engine + ${project.version} + + + ${project.groupId} + seata-saga-rm + ${project.version} + + + ${project.groupId} + seata-saga-tm + ${project.version} + + + ${project.groupId} + seata-serializer-all + ${project.version} + + + \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java new file mode 100644 index 0000000..50f2fbf --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/config/DbStateMachineConfig.java @@ -0,0 +1,200 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.config; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import io.seata.config.Configuration; +import io.seata.config.ConfigurationFactory; +import io.seata.core.constants.ConfigurationKeys; +import io.seata.saga.engine.impl.DefaultStateMachineConfig; +import io.seata.saga.engine.serializer.impl.ParamsSerializer; +import io.seata.saga.engine.store.db.DbAndReportTcStateLogStore; +import io.seata.saga.engine.store.db.DbStateLangStore; +import io.seata.saga.tm.DefaultSagaTransactionalTemplate; +import io.seata.saga.tm.SagaTransactionalTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.StringUtils; + +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE; +import static io.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER; + +/** + * DbStateMachineConfig + * + * @author lorne.cl + */ +public class DbStateMachineConfig extends DefaultStateMachineConfig implements DisposableBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(DbStateMachineConfig.class); + + private DataSource dataSource; + private String applicationId; + private String txServiceGroup; + private String tablePrefix = "seata_"; + private String dbType; + private SagaTransactionalTemplate sagaTransactionalTemplate; + private boolean rmReportSuccessEnable = DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE; + private boolean sagaBranchRegisterEnable = DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE; + + + public DbStateMachineConfig() { + try { + Configuration configuration = ConfigurationFactory.getInstance(); + if (configuration != null) { + this.rmReportSuccessEnable = configuration.getBoolean(ConfigurationKeys.CLIENT_REPORT_SUCCESS_ENABLE, DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE); + this.sagaBranchRegisterEnable = configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_BRANCH_REGISTER_ENABLE, DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE); + setSagaJsonParser(configuration.getConfig(ConfigurationKeys.CLIENT_SAGA_JSON_PARSER, DEFAULT_SAGA_JSON_PARSER)); + this.applicationId = configuration.getConfig(ConfigurationKeys.APPLICATION_ID); + this.txServiceGroup = configuration.getConfig(ConfigurationKeys.TX_SERVICE_GROUP); + setSagaRetryPersistModeUpdate(configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE, + DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE)); + setSagaCompensatePersistModeUpdate(configuration.getBoolean(ConfigurationKeys.CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE, + DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE)); + } + } catch (Exception e) { + LOGGER.warn("Load SEATA configuration failed, use default configuration instead.", e); + } + } + + public static String getDbTypeFromDataSource(DataSource dataSource) throws SQLException { + try (Connection con = dataSource.getConnection()) { + DatabaseMetaData metaData = con.getMetaData(); + return metaData.getDatabaseProductName(); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + + dbType = getDbTypeFromDataSource(dataSource); + + if (getStateLogStore() == null) { + DbAndReportTcStateLogStore dbStateLogStore = new DbAndReportTcStateLogStore(); + dbStateLogStore.setDataSource(dataSource); + dbStateLogStore.setTablePrefix(tablePrefix); + dbStateLogStore.setDbType(dbType); + dbStateLogStore.setDefaultTenantId(getDefaultTenantId()); + dbStateLogStore.setSeqGenerator(getSeqGenerator()); + + if (StringUtils.hasLength(getSagaJsonParser())) { + ParamsSerializer paramsSerializer = new ParamsSerializer(); + paramsSerializer.setJsonParserName(getSagaJsonParser()); + dbStateLogStore.setParamsSerializer(paramsSerializer); + } + + if (sagaTransactionalTemplate == null) { + DefaultSagaTransactionalTemplate defaultSagaTransactionalTemplate + = new DefaultSagaTransactionalTemplate(); + defaultSagaTransactionalTemplate.setApplicationContext(getApplicationContext()); + defaultSagaTransactionalTemplate.setApplicationId(applicationId); + defaultSagaTransactionalTemplate.setTxServiceGroup(txServiceGroup); + defaultSagaTransactionalTemplate.afterPropertiesSet(); + sagaTransactionalTemplate = defaultSagaTransactionalTemplate; + } + + dbStateLogStore.setSagaTransactionalTemplate(sagaTransactionalTemplate); + + setStateLogStore(dbStateLogStore); + } + + if (getStateLangStore() == null) { + DbStateLangStore dbStateLangStore = new DbStateLangStore(); + dbStateLangStore.setDataSource(dataSource); + dbStateLangStore.setTablePrefix(tablePrefix); + dbStateLangStore.setDbType(dbType); + + setStateLangStore(dbStateLangStore); + } + + super.afterPropertiesSet();//must execute after StateLangStore initialized + } + + @Override + public void destroy() throws Exception { + if ((sagaTransactionalTemplate != null) && (sagaTransactionalTemplate instanceof DisposableBean)) { + ((DisposableBean) sagaTransactionalTemplate).destroy(); + } + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getTxServiceGroup() { + return txServiceGroup; + } + + public void setTxServiceGroup(String txServiceGroup) { + this.txServiceGroup = txServiceGroup; + } + + public void setSagaTransactionalTemplate(SagaTransactionalTemplate sagaTransactionalTemplate) { + this.sagaTransactionalTemplate = sagaTransactionalTemplate; + } + + public String getTablePrefix() { + return tablePrefix; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + public String getDbType() { + return dbType; + } + + public void setDbType(String dbType) { + this.dbType = dbType; + } + + public boolean isRmReportSuccessEnable() { + return rmReportSuccessEnable; + } + + public boolean isSagaBranchRegisterEnable() { + return sagaBranchRegisterEnable; + } + + public void setSagaBranchRegisterEnable(boolean sagaBranchRegisterEnable) { + this.sagaBranchRegisterEnable = sagaBranchRegisterEnable; + } + + public void setRmReportSuccessEnable(boolean rmReportSuccessEnable) { + this.rmReportSuccessEnable = rmReportSuccessEnable; + } +} diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/pcext/interceptors/InSagaBranchHandlerInterceptor.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/pcext/interceptors/InSagaBranchHandlerInterceptor.java new file mode 100644 index 0000000..3109b0b --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/pcext/interceptors/InSagaBranchHandlerInterceptor.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.interceptors; + +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.StringUtils; +import io.seata.core.context.RootContext; +import io.seata.core.model.BranchType; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.tm.api.GlobalTransaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * InSagaBranchHandler Interceptor + * + * @author wang.liang + */ +@LoadLevel(name = "InSagaBranch", order = 50) +public class InSagaBranchHandlerInterceptor implements StateHandlerInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(InSagaBranchHandlerInterceptor.class); + + @Override + public boolean match(Class clazz) { + // This interceptor will intercept all types of InterceptableStateHandler + return clazz != null; + } + + @Override + public void preProcess(ProcessContext context) throws EngineExecutionException { + // get xid + String xid = this.getXidFromProcessContext(context); + if (StringUtils.isBlank(xid)) { + LOGGER.warn("There is no xid in the process context."); + return; + } + + // logger.warn if previousXid is not equals to xid + if (LOGGER.isWarnEnabled()) { + String previousXid = RootContext.getXID(); + if (previousXid != null) { + if (!StringUtils.equalsIgnoreCase(previousXid, xid)) { + LOGGER.warn("xid in change from {} to {}, Please don't use state machine engine in other global transaction.", + previousXid, xid); + } + } + } + + // bind xid and branchType + RootContext.bind(xid); + RootContext.bindBranchType(BranchType.SAGA); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("[{}] Begin process the state instance in the saga branch.", xid); + } + } + + @Override + public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException { + // unbind xid and branchType + String xid = RootContext.unbind(); + RootContext.unbindBranchType(); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("[{}] Finish process the state instance in the saga branch.", xid); + } + } + + /** + * Gets xid from saga process context. + * + * @return the xid + */ + protected String getXidFromProcessContext(ProcessContext context) { + String xid = null; + Map contextVariable = (Map) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + if (contextVariable != null && contextVariable.containsKey(DomainConstants.VAR_NAME_GLOBAL_TX)) { + GlobalTransaction globalTransaction = (GlobalTransaction) contextVariable.get(DomainConstants.VAR_NAME_GLOBAL_TX); + xid = globalTransaction.getXid(); + } else { + StateMachineInstance stateMachineInstance = (StateMachineInstance) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_INST); + if (stateMachineInstance != null) { + xid = stateMachineInstance.getId(); + } + } + return xid; + } +} diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/Serializer.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/Serializer.java new file mode 100644 index 0000000..9f109a7 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/Serializer.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.serializer; + +/** + * Object serializer + * + * @author lorne.cl + */ +public interface Serializer { + + /** + * serialize + * + * @param object + * @return + */ + T serialize(S object); + + /** + * deserialize + * + * @param obj + * @return + */ + S deserialize(T obj); +} \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ExceptionSerializer.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ExceptionSerializer.java new file mode 100644 index 0000000..7c281d9 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ExceptionSerializer.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.serializer.impl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import io.seata.saga.engine.serializer.Serializer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Exception serializer + * + * @author lorne.cl + */ +public class ExceptionSerializer implements Serializer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionSerializer.class); + + public static byte[] serializeByObjectOutput(Object o) { + + byte[] result = null; + if (o != null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(o); + oos.flush(); + result = baos.toByteArray(); + } catch (IOException e) { + LOGGER.error("serializer failed: {}", o.getClass(), e); + throw new RuntimeException("IO Create Error", e); + } + } + return result; + } + + public static T deserializeByObjectInputStream(byte[] bytes, Class valueType) { + + if (bytes == null) { + return null; + } + + Object result = deserializeByObjectInputStream(bytes); + return valueType.cast(result); + } + + public static Object deserializeByObjectInputStream(byte[] bytes) { + + Object result = null; + if (bytes != null) { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try (ObjectInputStream ois = new ObjectInputStream(bais)) { + result = ois.readObject(); + } catch (IOException e) { + LOGGER.error("deserialize failed:", e); + throw new RuntimeException("IO Create Error", e); + } catch (ClassNotFoundException e) { + LOGGER.error("deserialize failed:", e); + throw new RuntimeException("Cannot find specified class", e); + } + } + return result; + } + + @Override + public byte[] serialize(Exception object) { + + return serializeByObjectOutput(object); + } + + @Override + public Exception deserialize(byte[] bytes) { + return deserializeByObjectInputStream(bytes, Exception.class); + } +} diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ParamsSerializer.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ParamsSerializer.java new file mode 100644 index 0000000..bce52e9 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/serializer/impl/ParamsSerializer.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.serializer.impl; + +import io.seata.saga.engine.serializer.Serializer; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.parser.JsonParser; +import io.seata.saga.statelang.parser.JsonParserFactory; + +/** + * Parameter serializer based on Fastjson + * + * @author lorne.cl + */ +public class ParamsSerializer implements Serializer { + + private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER; + + @Override + public String serialize(Object params) { + if (params != null) { + JsonParser jsonParser = JsonParserFactory.getJsonParser(jsonParserName); + if (jsonParser == null) { + throw new RuntimeException("Cannot find JsonParer by name: " + jsonParserName); + } + return jsonParser.toJsonString(params, false); + } + return null; + } + + @Override + public Object deserialize(String json) { + if (json != null) { + JsonParser jsonParser = JsonParserFactory.getJsonParser(jsonParserName); + if (jsonParser == null) { + throw new RuntimeException("Cannot find JsonParer by name: " + jsonParserName); + } + return jsonParser.parse(json, Object.class, false); + } + return null; + } + + public String getJsonParserName() { + return jsonParserName; + } + + public void setJsonParserName(String jsonParserName) { + this.jsonParserName = jsonParserName; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/AbstractStore.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/AbstractStore.java new file mode 100644 index 0000000..85ccc96 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/AbstractStore.java @@ -0,0 +1,210 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store.db; + +import io.seata.common.util.BeanUtils; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.sql.DataSource; + +import io.seata.common.exception.StoreException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract store + * + * @author lorne.cl + */ +public class AbstractStore { + + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractStore.class); + + protected DataSource dataSource; + + protected String dbType; + + protected String tablePrefix; + + public static void closeSilent(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + LOGGER.info(e.getMessage(), e); + } + } + } + + protected T selectOne(String sql, ResultSetToObject resultSetToObject, Object... args) { + Connection connection = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + try { + connection = dataSource.getConnection(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Preparing SQL statement: {}", sql); + } + + stmt = connection.prepareStatement(sql); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args)); + } + + for (int i = 0; i < args.length; i++) { + stmt.setObject(i + 1, args[i]); + } + resultSet = stmt.executeQuery(); + if (resultSet.next()) { + return resultSetToObject.toObject(resultSet); + } + } catch (SQLException e) { + throw new StoreException(e); + } finally { + closeSilent(resultSet); + closeSilent(stmt); + closeSilent(connection); + } + return null; + } + + protected List selectList(String sql, ResultSetToObject resultSetToObject, Object... args) { + Connection connection = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + try { + connection = dataSource.getConnection(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Preparing SQL: {}", sql); + } + + stmt = connection.prepareStatement(sql); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args)); + } + + for (int i = 0; i < args.length; i++) { + stmt.setObject(i + 1, args[i]); + } + resultSet = stmt.executeQuery(); + List list = new ArrayList<>(); + while (resultSet.next()) { + list.add(resultSetToObject.toObject(resultSet)); + } + return list; + } catch (SQLException e) { + throw new StoreException(e); + } finally { + closeSilent(resultSet); + closeSilent(stmt); + closeSilent(connection); + } + } + + protected int executeUpdate(String sql, ObjectToStatement objectToStatement, T o) { + Connection connection = null; + PreparedStatement stmt = null; + try { + connection = dataSource.getConnection(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Preparing SQL: {}", sql); + } + + stmt = connection.prepareStatement(sql); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("setting params to PreparedStatement: {}", BeanUtils.beanToString(o)); + } + + objectToStatement.toStatement(o, stmt); + int count = stmt.executeUpdate(); + if (!connection.getAutoCommit()) { + connection.commit(); + } + return count; + } catch (SQLException e) { + throw new StoreException(e); + } finally { + closeSilent(stmt); + closeSilent(connection); + } + } + + protected int executeUpdate(String sql, Object... args) { + Connection connection = null; + PreparedStatement stmt = null; + try { + connection = dataSource.getConnection(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Preparing SQL: {}", sql); + } + + stmt = connection.prepareStatement(sql); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("setting params to PreparedStatement: {}", Arrays.toString(args)); + } + + for (int i = 0; i < args.length; i++) { + stmt.setObject(i + 1, args[i]); + } + int count = stmt.executeUpdate(); + if (!connection.getAutoCommit()) { + connection.commit(); + } + return count; + } catch (SQLException e) { + throw new StoreException(e); + } finally { + closeSilent(stmt); + closeSilent(connection); + } + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } + + public void setDbType(String dbType) { + this.dbType = dbType; + } + + public void setTablePrefix(String tablePrefix) { + this.tablePrefix = tablePrefix; + } + + protected interface ResultSetToObject { + + T toObject(ResultSet resultSet) throws SQLException; + } + + protected interface ObjectToStatement { + + void toStatement(T o, PreparedStatement statement) throws SQLException; + } +} diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbAndReportTcStateLogStore.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbAndReportTcStateLogStore.java new file mode 100644 index 0000000..8894246 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbAndReportTcStateLogStore.java @@ -0,0 +1,912 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store.db; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.seata.common.Constants; +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.StoreException; +import io.seata.common.util.CollectionUtils; +import io.seata.core.context.RootContext; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.GlobalStatus; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.config.DbStateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.impl.DefaultStateMachineConfig; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.sequence.SeqGenerator; +import io.seata.saga.engine.serializer.Serializer; +import io.seata.saga.engine.serializer.impl.ExceptionSerializer; +import io.seata.saga.engine.serializer.impl.ParamsSerializer; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import io.seata.saga.statelang.domain.impl.StateInstanceImpl; +import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; +import io.seata.saga.tm.SagaTransactionalTemplate; +import io.seata.tm.api.GlobalTransaction; +import io.seata.tm.api.TransactionalExecutor.ExecutionException; +import io.seata.tm.api.transaction.TransactionInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * State machine logs and definitions persist to database and report status to TC (Transaction Coordinator) + * + * @author lorne.cl + */ +public class DbAndReportTcStateLogStore extends AbstractStore implements StateLogStore { + + private static final Logger LOGGER = LoggerFactory.getLogger( + DbAndReportTcStateLogStore.class); + private static final StateMachineInstanceToStatementForInsert STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_INSERT + = new StateMachineInstanceToStatementForInsert(); + private static final StateMachineInstanceToStatementForUpdate STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_UPDATE + = new StateMachineInstanceToStatementForUpdate(); + private static final ResultSetToStateMachineInstance RESULT_SET_TO_STATE_MACHINE_INSTANCE + = new ResultSetToStateMachineInstance(); + private static final StateInstanceToStatementForInsert STATE_INSTANCE_TO_STATEMENT_FOR_INSERT + = new StateInstanceToStatementForInsert(); + private static final StateInstanceToStatementForUpdate STATE_INSTANCE_TO_STATEMENT_FOR_UPDATE + = new StateInstanceToStatementForUpdate(); + private static final ResultSetToStateInstance RESULT_SET_TO_STATE_INSTANCE = new ResultSetToStateInstance(); + private SagaTransactionalTemplate sagaTransactionalTemplate; + private Serializer paramsSerializer = new ParamsSerializer(); + private Serializer exceptionSerializer = new ExceptionSerializer(); + private StateLogStoreSqls stateLogStoreSqls; + private String defaultTenantId; + private SeqGenerator seqGenerator; + + @Override + public void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context) { + if (machineInstance != null) { + //if parentId is not null, machineInstance is a SubStateMachine, do not start a new global transaction, + //use parent transaction instead. + String parentId = machineInstance.getParentId(); + if (StringUtils.isEmpty(parentId)) { + beginTransaction(machineInstance, context); + } + + try { + if (StringUtils.isEmpty(machineInstance.getId()) && seqGenerator != null) { + machineInstance.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE_INST)); + } + + // bind SAGA branch type + RootContext.bindBranchType(BranchType.SAGA); + + // save to db + machineInstance.setSerializedStartParams(paramsSerializer.serialize(machineInstance.getStartParams())); + int effect = executeUpdate(stateLogStoreSqls.getRecordStateMachineStartedSql(dbType), + STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_INSERT, machineInstance); + if (effect < 1) { + throw new StoreException("StateMachineInstance record start error, Xid: " + machineInstance.getId(), + FrameworkErrorCode.OperationDenied); + } + } catch (StoreException e) { + LOGGER.error("Record statemachine start error: {}, StateMachine: {}, XID: {}, Reason: {}", + e.getErrcode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e); + // clear + RootContext.unbind(); + RootContext.unbindBranchType(); + if (sagaTransactionalTemplate != null) { + sagaTransactionalTemplate.cleanUp(); + } + throw e; + } + } + } + + protected void beginTransaction(StateMachineInstance machineInstance, ProcessContext context) { + if (sagaTransactionalTemplate != null) { + + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + TransactionInfo transactionInfo = new TransactionInfo(); + transactionInfo.setTimeOut(stateMachineConfig.getTransOperationTimeout()); + transactionInfo.setName(Constants.SAGA_TRANS_NAME_PREFIX + machineInstance.getStateMachine().getName()); + try { + GlobalTransaction globalTransaction = sagaTransactionalTemplate.beginTransaction(transactionInfo); + machineInstance.setId(globalTransaction.getXid()); + + context.setVariable(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction); + Map machineContext = machineInstance.getContext(); + if (machineContext != null) { + machineContext.put(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction); + } + } catch (ExecutionException e) { + String xid = null; + if (e.getTransaction() != null) { + xid = e.getTransaction().getXid(); + } + throw new EngineExecutionException(e, + e.getCode() + ", TransName:" + transactionInfo.getName() + ", XID: " + xid + ", Reason: " + e + .getMessage(), FrameworkErrorCode.TransactionManagerError); + } + finally { + if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_ASYNC_EXECUTION))) { + RootContext.unbind(); + RootContext.unbindBranchType(); + } + } + } + } + + @Override + public void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context) { + if (machineInstance != null) { + try { + // save to db + Map endParams = machineInstance.getEndParams(); + if (endParams != null) { + endParams.remove(DomainConstants.VAR_NAME_GLOBAL_TX); + } + + // if success, clear exception + if (ExecutionStatus.SU.equals(machineInstance.getStatus()) && machineInstance.getException() != null) { + machineInstance.setException(null); + } + + machineInstance.setSerializedEndParams(paramsSerializer.serialize(machineInstance.getEndParams())); + machineInstance.setSerializedException(exceptionSerializer.serialize(machineInstance.getException())); + int effect = executeUpdate(stateLogStoreSqls.getRecordStateMachineFinishedSql(dbType), + STATE_MACHINE_INSTANCE_TO_STATEMENT_FOR_UPDATE, machineInstance); + if (effect < 1) { + LOGGER.warn("StateMachineInstance[{}] is recovery by server, skip recordStateMachineFinished.", machineInstance.getId()); + } else { + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + if (EngineUtils.isTimeout(machineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) { + LOGGER.warn("StateMachineInstance[{}] is execution timeout, skip report transaction finished to server.", machineInstance.getId()); + } else if (StringUtils.isEmpty(machineInstance.getParentId())) { + //if parentId is not null, machineInstance is a SubStateMachine, do not report global transaction. + reportTransactionFinished(machineInstance, context); + } + } + } finally { + RootContext.unbind(); + RootContext.unbindBranchType(); + } + } + } + + protected void reportTransactionFinished(StateMachineInstance machineInstance, ProcessContext context) { + if (sagaTransactionalTemplate != null) { + try { + GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context); + if (globalTransaction == null) { + + throw new EngineExecutionException("Global transaction is not exists", + FrameworkErrorCode.ObjectNotExists); + } + + GlobalStatus globalStatus; + if (ExecutionStatus.SU.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + globalStatus = GlobalStatus.Committed; + } else if (ExecutionStatus.SU.equals(machineInstance.getCompensationStatus())) { + globalStatus = GlobalStatus.Rollbacked; + } else if (ExecutionStatus.FA.equals(machineInstance.getCompensationStatus()) || ExecutionStatus.UN + .equals(machineInstance.getCompensationStatus())) { + globalStatus = GlobalStatus.RollbackRetrying; + } else if (ExecutionStatus.FA.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + globalStatus = GlobalStatus.Finished; + } else if (ExecutionStatus.UN.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + globalStatus = GlobalStatus.CommitRetrying; + } else { + globalStatus = GlobalStatus.UnKnown; + } + sagaTransactionalTemplate.reportTransaction(globalTransaction, globalStatus); + } catch (ExecutionException e) { + LOGGER.error("Report transaction finish to server error: {}, StateMachine: {}, XID: {}, Reason: {}", + e.getCode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e); + } catch (TransactionException e) { + LOGGER.error("Report transaction finish to server error: {}, StateMachine: {}, XID: {}, Reason: {}", + e.getCode(), machineInstance.getStateMachine().getName(), machineInstance.getId(), e.getMessage(), e); + } finally { + // clear + RootContext.unbind(); + RootContext.unbindBranchType(); + sagaTransactionalTemplate.triggerAfterCompletion(); + sagaTransactionalTemplate.cleanUp(); + } + } + } + + @Override + public void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context) { + + if (machineInstance != null) { + //save to db + Date gmtUpdated = new Date(); + int effect = executeUpdate(stateLogStoreSqls.getUpdateStateMachineRunningStatusSql(dbType), machineInstance.isRunning(), new Timestamp(gmtUpdated.getTime()), + machineInstance.getId(), new Timestamp(machineInstance.getGmtUpdated().getTime())); + if (effect < 1) { + throw new EngineExecutionException( + "StateMachineInstance [id:" + machineInstance.getId() + "] is recovered by an other execution, restart denied", FrameworkErrorCode.OperationDenied); + } + machineInstance.setGmtUpdated(gmtUpdated); + } + } + + @Override + public void recordStateStarted(StateInstance stateInstance, ProcessContext context) { + if (stateInstance != null) { + + boolean isUpdateMode = isUpdateMode(stateInstance, context); + + // if this state is for retry, do not register branch + if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) { + if (isUpdateMode) { + stateInstance.setId(stateInstance.getStateIdRetriedFor()); + } else { + // generate id by default + stateInstance.setId(generateRetryStateInstanceId(stateInstance)); + } + } + // if this state is for compensation, do not register branch + else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) { + stateInstance.setId(generateCompensateStateInstanceId(stateInstance, isUpdateMode)); + } else { + branchRegister(stateInstance, context); + } + + if (StringUtils.isEmpty(stateInstance.getId()) && seqGenerator != null) { + stateInstance.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_INST)); + } + + stateInstance.setSerializedInputParams(paramsSerializer.serialize(stateInstance.getInputParams())); + if (!isUpdateMode) { + executeUpdate(stateLogStoreSqls.getRecordStateStartedSql(dbType), + STATE_INSTANCE_TO_STATEMENT_FOR_INSERT, stateInstance); + } else { + // if this retry/compensate state do not need persist, just update last inst + executeUpdate(stateLogStoreSqls.getUpdateStateExecutionStatusSql(dbType), + stateInstance.getStatus().name(), new Timestamp(System.currentTimeMillis()), + stateInstance.getMachineInstanceId(), stateInstance.getId()); + } + } + } + + protected void branchRegister(StateInstance stateInstance, ProcessContext context) { + if (sagaTransactionalTemplate != null) { + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (stateMachineConfig instanceof DbStateMachineConfig + && !((DbStateMachineConfig)stateMachineConfig).isSagaBranchRegisterEnable()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sagaBranchRegisterEnable = false, skip register branch. state[" + stateInstance.getName() + "]"); + } + return; + } + + //Register branch + try { + StateMachineInstance machineInstance = stateInstance.getStateMachineInstance(); + GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context); + if (globalTransaction == null) { + throw new EngineExecutionException("Global transaction is not exists", FrameworkErrorCode.ObjectNotExists); + } + + String resourceId = stateInstance.getStateMachineInstance().getStateMachine().getName() + "#" + stateInstance.getName(); + long branchId = sagaTransactionalTemplate.branchRegister(resourceId, null, globalTransaction.getXid(), null, null); + stateInstance.setId(String.valueOf(branchId)); + } catch (TransactionException e) { + throw new EngineExecutionException(e, + "Branch transaction error: " + e.getCode() + ", StateMachine:" + stateInstance.getStateMachineInstance() + .getStateMachine().getName() + ", XID: " + stateInstance.getStateMachineInstance().getId() + ", State:" + + stateInstance.getName() + ", stateId: " + stateInstance.getId() + ", Reason: " + e.getMessage(), + FrameworkErrorCode.TransactionManagerError); + } catch (ExecutionException e) { + throw new EngineExecutionException(e, + "Branch transaction error: " + e.getCode() + ", StateMachine:" + stateInstance.getStateMachineInstance() + .getStateMachine().getName() + ", XID: " + stateInstance.getStateMachineInstance().getId() + ", State:" + + stateInstance.getName() + ", stateId: " + stateInstance.getId() + ", Reason: " + e.getMessage(), + FrameworkErrorCode.TransactionManagerError); + } + } + } + + protected GlobalTransaction getGlobalTransaction(StateMachineInstance machineInstance, ProcessContext context) + throws ExecutionException, TransactionException { + GlobalTransaction globalTransaction = (GlobalTransaction) context.getVariable(DomainConstants.VAR_NAME_GLOBAL_TX); + if (globalTransaction == null) { + String xid; + String parentId = machineInstance.getParentId(); + if (StringUtils.isEmpty(parentId)) { + xid = machineInstance.getId(); + } else { + xid = parentId.substring(0, parentId.lastIndexOf(DomainConstants.SEPERATOR_PARENT_ID)); + } + globalTransaction = sagaTransactionalTemplate.reloadTransaction(xid); + if (globalTransaction != null) { + context.setVariable(DomainConstants.VAR_NAME_GLOBAL_TX, globalTransaction); + } + } + return globalTransaction; + } + + /** + * generate retry state instance id based on original state instance id + * ${originalStateInstanceId}.${retryCount} + * @param stateInstance + * @return + */ + private String generateRetryStateInstanceId(StateInstance stateInstance) { + String originalStateInstId = stateInstance.getStateIdRetriedFor(); + int maxIndex = 1; + Map stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap(); + StateInstance originalStateInst = stateInstanceMap.get(stateInstance.getStateIdRetriedFor()); + while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) { + originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor()); + int idIndex = getIdIndex(originalStateInst.getId(), "."); + maxIndex = idIndex > maxIndex ? idIndex : maxIndex; + maxIndex++; + } + if (originalStateInst != null) { + originalStateInstId = originalStateInst.getId(); + } + return originalStateInstId + "." + maxIndex; + } + + /** + * generate compensate state instance id based on original state instance id + * ${originalStateInstanceId}-${retryCount} + * @param stateInstance + * @return + */ + private String generateCompensateStateInstanceId(StateInstance stateInstance, boolean isUpdateMode) { + String originalCompensateStateInstId = stateInstance.getStateIdCompensatedFor(); + int maxIndex = 1; + // if update mode, means update last compensate inst + if (isUpdateMode) { + return originalCompensateStateInstId + "-" + maxIndex; + } + + for (int i = 0; i < stateInstance.getStateMachineInstance().getStateList().size(); i++) { + StateInstance aStateInstance = stateInstance.getStateMachineInstance().getStateList().get(i); + if (aStateInstance != stateInstance + && originalCompensateStateInstId.equals(aStateInstance.getStateIdCompensatedFor())) { + int idIndex = getIdIndex(aStateInstance.getId(), "-"); + maxIndex = idIndex > maxIndex ? idIndex : maxIndex; + maxIndex++; + } + } + return originalCompensateStateInstId + "-" + maxIndex; + } + + private int getIdIndex(String stateInstanceId, String separator) { + if (StringUtils.hasLength(stateInstanceId)) { + int start = stateInstanceId.lastIndexOf(separator); + if (start > 0) { + String indexStr = stateInstanceId.substring(start + 1, stateInstanceId.length()); + try { + return Integer.parseInt(indexStr); + } catch (NumberFormatException e) { + LOGGER.warn("get stateInstance id index failed", e); + } + } + } + return -1; + } + + private boolean isUpdateMode(StateInstance stateInstance, ProcessContext context) { + DefaultStateMachineConfig stateMachineConfig = (DefaultStateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context); + StateMachine stateMachine = stateInstance.getStateMachineInstance().getStateMachine(); + + if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) { + + if (null != state.isRetryPersistModeUpdate()) { + return state.isRetryPersistModeUpdate(); + } else if (null != stateMachine.isRetryPersistModeUpdate()) { + return stateMachine.isRetryPersistModeUpdate(); + } + return stateMachineConfig.isSagaRetryPersistModeUpdate(); + + } else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) { + + // find if this compensate has been executed + for (int i = 0; i < stateInstance.getStateMachineInstance().getStateList().size(); i++) { + StateInstance aStateInstance = stateInstance.getStateMachineInstance().getStateList().get(i); + if (aStateInstance.isForCompensation() && aStateInstance.getName().equals(stateInstance.getName())) { + if (null != state.isCompensatePersistModeUpdate()) { + return state.isCompensatePersistModeUpdate(); + } else if (null != stateMachine.isCompensatePersistModeUpdate()) { + return stateMachine.isCompensatePersistModeUpdate(); + } + return stateMachineConfig.isSagaCompensatePersistModeUpdate(); + } + } + return false; + } + return false; + } + + @Override + public void recordStateFinished(StateInstance stateInstance, ProcessContext context) { + if (stateInstance != null) { + + stateInstance.setSerializedOutputParams(paramsSerializer.serialize(stateInstance.getOutputParams())); + stateInstance.setSerializedException(exceptionSerializer.serialize(stateInstance.getException())); + executeUpdate(stateLogStoreSqls.getRecordStateFinishedSql(dbType), STATE_INSTANCE_TO_STATEMENT_FOR_UPDATE, + stateInstance); + + //A switch to skip branch report on branch success, in order to optimize performance + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + if (!(stateMachineConfig instanceof DbStateMachineConfig + && !((DbStateMachineConfig)stateMachineConfig).isRmReportSuccessEnable() + && ExecutionStatus.SU.equals(stateInstance.getStatus()))) { + branchReport(stateInstance, context); + } + } + } + + protected void branchReport(StateInstance stateInstance, ProcessContext context) { + if (sagaTransactionalTemplate != null) { + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (stateMachineConfig instanceof DbStateMachineConfig + && !((DbStateMachineConfig)stateMachineConfig).isSagaBranchRegisterEnable()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sagaBranchRegisterEnable = false, skip branch report. state[" + stateInstance.getName() + "]"); + } + return; + } + + BranchStatus branchStatus = null; + //find out the original state instance, only the original state instance is registered on the server, and its status should + // be reported. + StateInstance originalStateInst = null; + if (StringUtils.hasLength(stateInstance.getStateIdRetriedFor())) { + + if (isUpdateMode(stateInstance, context)) { + originalStateInst = stateInstance; + } else { + originalStateInst = findOutOriginalStateInstanceOfRetryState(stateInstance); + } + + if (ExecutionStatus.SU.equals(stateInstance.getStatus())) { + branchStatus = BranchStatus.PhaseTwo_Committed; + } else if (ExecutionStatus.FA.equals(stateInstance.getStatus()) || ExecutionStatus.UN.equals( + stateInstance.getStatus())) { + branchStatus = BranchStatus.PhaseOne_Failed; + } else { + branchStatus = BranchStatus.Unknown; + } + + } else if (StringUtils.hasLength(stateInstance.getStateIdCompensatedFor())) { + + if (isUpdateMode(stateInstance, context)) { + originalStateInst = stateInstance.getStateMachineInstance().getStateMap().get( + stateInstance.getStateIdCompensatedFor()); + } else { + originalStateInst = findOutOriginalStateInstanceOfCompensateState(stateInstance); + } + } + + if (originalStateInst == null) { + originalStateInst = stateInstance; + } + + if (branchStatus == null) { + if (ExecutionStatus.SU.equals(originalStateInst.getStatus()) && originalStateInst.getCompensationStatus() == null) { + branchStatus = BranchStatus.PhaseTwo_Committed; + } else if (ExecutionStatus.SU.equals(originalStateInst.getCompensationStatus())) { + branchStatus = BranchStatus.PhaseTwo_Rollbacked; + } else if (ExecutionStatus.FA.equals(originalStateInst.getCompensationStatus()) + || ExecutionStatus.UN.equals(originalStateInst.getCompensationStatus())) { + branchStatus = BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } else if ((ExecutionStatus.FA.equals(originalStateInst.getStatus()) || ExecutionStatus.UN.equals( + originalStateInst.getStatus())) + && originalStateInst.getCompensationStatus() == null) { + branchStatus = BranchStatus.PhaseOne_Failed; + } else { + branchStatus = BranchStatus.Unknown; + } + } + + try { + StateMachineInstance machineInstance = stateInstance.getStateMachineInstance(); + GlobalTransaction globalTransaction = getGlobalTransaction(machineInstance, context); + + if (globalTransaction == null) { + throw new EngineExecutionException("Global transaction is not exists", FrameworkErrorCode.ObjectNotExists); + } + + sagaTransactionalTemplate.branchReport(globalTransaction.getXid(), Long.parseLong(originalStateInst.getId()), branchStatus, + null); + } catch (TransactionException e) { + LOGGER.error( + "Report branch status to server error: {}, StateMachine:{}, StateName:{}, XID: {}, branchId: {}, branchStatus:{}," + + " Reason:{} " + , e.getCode() + , originalStateInst.getStateMachineInstance().getStateMachine().getName() + , originalStateInst.getName() + , originalStateInst.getStateMachineInstance().getId() + , originalStateInst.getId() + , branchStatus + , e.getMessage() + , e); + } catch (ExecutionException e) { + LOGGER.error( + "Report branch status to server error: {}, StateMachine:{}, StateName:{}, XID: {}, branchId: {}, branchStatus:{}," + + " Reason:{} " + , e.getCode() + , originalStateInst.getStateMachineInstance().getStateMachine().getName() + , originalStateInst.getName() + , originalStateInst.getStateMachineInstance().getId() + , originalStateInst.getId() + , branchStatus + , e.getMessage() + , e); + } + } + } + + private StateInstance findOutOriginalStateInstanceOfRetryState(StateInstance stateInstance) { + StateInstance originalStateInst; + Map stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap(); + originalStateInst = stateInstanceMap.get(stateInstance.getStateIdRetriedFor()); + while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) { + originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor()); + } + return originalStateInst; + } + + private StateInstance findOutOriginalStateInstanceOfCompensateState(StateInstance stateInstance) { + StateInstance originalStateInst; + Map stateInstanceMap = stateInstance.getStateMachineInstance().getStateMap(); + originalStateInst = stateInstance.getStateMachineInstance().getStateMap().get(stateInstance.getStateIdCompensatedFor()); + while (StringUtils.hasLength(originalStateInst.getStateIdRetriedFor())) { + originalStateInst = stateInstanceMap.get(originalStateInst.getStateIdRetriedFor()); + } + return originalStateInst; + } + + @Override + public StateMachineInstance getStateMachineInstance(String stateMachineInstanceId) { + StateMachineInstance stateMachineInstance = selectOne(stateLogStoreSqls.getGetStateMachineInstanceByIdSql(dbType), + RESULT_SET_TO_STATE_MACHINE_INSTANCE, stateMachineInstanceId); + if (stateMachineInstance == null) { + return null; + } + List stateInstanceList = queryStateInstanceListByMachineInstanceId(stateMachineInstanceId); + for (StateInstance stateInstance : stateInstanceList) { + stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance); + } + deserializeParamsAndException(stateMachineInstance); + + return stateMachineInstance; + } + + @Override + public StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId) { + if (StringUtils.isEmpty(tenantId)) { + tenantId = defaultTenantId; + } + StateMachineInstance stateMachineInstance = selectOne( + stateLogStoreSqls.getGetStateMachineInstanceByBusinessKeySql(dbType), RESULT_SET_TO_STATE_MACHINE_INSTANCE, + businessKey, tenantId); + if (stateMachineInstance == null) { + return null; + } + List stateInstanceList = queryStateInstanceListByMachineInstanceId(stateMachineInstance.getId()); + for (StateInstance stateInstance : stateInstanceList) { + stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance); + } + deserializeParamsAndException(stateMachineInstance); + + return stateMachineInstance; + } + + private void deserializeParamsAndException(StateMachineInstance stateMachineInstance) { + byte[] serializedException = (byte[]) stateMachineInstance.getSerializedException(); + if (serializedException != null) { + stateMachineInstance.setException((Exception) exceptionSerializer.deserialize(serializedException)); + } + + String serializedStartParams = (String) stateMachineInstance.getSerializedStartParams(); + if (StringUtils.hasLength(serializedStartParams)) { + stateMachineInstance.setStartParams( + (Map) paramsSerializer.deserialize(serializedStartParams)); + } + + String serializedEndParams = (String) stateMachineInstance.getSerializedEndParams(); + if (StringUtils.hasLength(serializedEndParams)) { + stateMachineInstance.setEndParams((Map) paramsSerializer.deserialize(serializedEndParams)); + } + } + + @Override + public List queryStateMachineInstanceByParentId(String parentId) { + return selectList(stateLogStoreSqls.getQueryStateMachineInstancesByParentIdSql(dbType), + RESULT_SET_TO_STATE_MACHINE_INSTANCE, parentId); + } + + @Override + public StateInstance getStateInstance(String stateInstanceId, String machineInstId) { + StateInstance stateInstance = selectOne( + stateLogStoreSqls.getGetStateInstanceByIdAndMachineInstanceIdSql(dbType), RESULT_SET_TO_STATE_INSTANCE, + machineInstId, stateInstanceId); + deserializeParamsAndException(stateInstance); + return stateInstance; + } + + private void deserializeParamsAndException(StateInstance stateInstance) { + if (stateInstance != null) { + String inputParams = (String) stateInstance.getSerializedInputParams(); + if (StringUtils.hasLength(inputParams)) { + stateInstance.setInputParams(paramsSerializer.deserialize(inputParams)); + } + String outputParams = (String) stateInstance.getSerializedOutputParams(); + if (StringUtils.hasLength(outputParams)) { + stateInstance.setOutputParams(paramsSerializer.deserialize(outputParams)); + } + byte[] serializedException = (byte[]) stateInstance.getSerializedException(); + if (serializedException != null) { + stateInstance.setException((Exception) exceptionSerializer.deserialize(serializedException)); + } + } + } + + @Override + public List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId) { + List stateInstanceList = selectList( + stateLogStoreSqls.getQueryStateInstancesByMachineInstanceIdSql(dbType), RESULT_SET_TO_STATE_INSTANCE, + stateMachineInstanceId); + + if (CollectionUtils.isEmpty(stateInstanceList)) { + return stateInstanceList; + } + StateInstance lastStateInstance = CollectionUtils.getLast(stateInstanceList); + if (lastStateInstance.getGmtEnd() == null) { + lastStateInstance.setStatus(ExecutionStatus.RU); + } + Map originStateMap = new HashMap<>(); + Map compensatedStateMap = new HashMap<>(); + Map retriedStateMap = new HashMap<>(); + for (StateInstance tempStateInstance : stateInstanceList) { + deserializeParamsAndException(tempStateInstance); + + if (StringUtils.hasText(tempStateInstance.getStateIdCompensatedFor())) { + putLastStateToMap(compensatedStateMap, tempStateInstance, tempStateInstance.getStateIdCompensatedFor()); + } else { + if (StringUtils.hasText(tempStateInstance.getStateIdRetriedFor())) { + putLastStateToMap(retriedStateMap, tempStateInstance, tempStateInstance.getStateIdRetriedFor()); + } + originStateMap.put(tempStateInstance.getId(), tempStateInstance); + } + } + + if (compensatedStateMap.size() != 0) { + for (StateInstance origState : originStateMap.values()) { + origState.setCompensationState(compensatedStateMap.get(origState.getId())); + } + } + + if (retriedStateMap.size() != 0) { + for (StateInstance origState : originStateMap.values()) { + if (retriedStateMap.containsKey(origState.getId())) { + origState.setIgnoreStatus(true); + } + } + } + return stateInstanceList; + } + + private void putLastStateToMap(Map resultMap, StateInstance newState, String key) { + if (!resultMap.containsKey(key)) { + resultMap.put(key, newState); + } else if (newState.getGmtEnd().after(resultMap.get(key).getGmtEnd())) { + StateInstance oldState = resultMap.remove(key); + oldState.setIgnoreStatus(true); + + resultMap.put(key, newState); + } else { + newState.setIgnoreStatus(true); + } + } + + public void setExceptionSerializer(Serializer exceptionSerializer) { + this.exceptionSerializer = exceptionSerializer; + } + + public SagaTransactionalTemplate getSagaTransactionalTemplate() { + return sagaTransactionalTemplate; + } + + public void setSagaTransactionalTemplate(SagaTransactionalTemplate sagaTransactionalTemplate) { + this.sagaTransactionalTemplate = sagaTransactionalTemplate; + } + + public Serializer getParamsSerializer() { + return paramsSerializer; + } + + public void setParamsSerializer(Serializer paramsSerializer) { + this.paramsSerializer = paramsSerializer; + } + + public String getDefaultTenantId() { + return defaultTenantId; + } + + public void setDefaultTenantId(String defaultTenantId) { + this.defaultTenantId = defaultTenantId; + } + + public void setSeqGenerator(SeqGenerator seqGenerator) { + this.seqGenerator = seqGenerator; + } + + @Override + public void setTablePrefix(String tablePrefix) { + super.setTablePrefix(tablePrefix); + this.stateLogStoreSqls = new StateLogStoreSqls(tablePrefix); + } + + private static class StateMachineInstanceToStatementForInsert implements ObjectToStatement { + @Override + public void toStatement(StateMachineInstance stateMachineInstance, PreparedStatement statement) + throws SQLException { + statement.setString(1, stateMachineInstance.getId()); + statement.setString(2, stateMachineInstance.getMachineId()); + statement.setString(3, stateMachineInstance.getTenantId()); + statement.setString(4, stateMachineInstance.getParentId()); + statement.setTimestamp(5, new Timestamp(stateMachineInstance.getGmtStarted().getTime())); + statement.setString(6, stateMachineInstance.getBusinessKey()); + statement.setObject(7, stateMachineInstance.getSerializedStartParams()); + statement.setBoolean(8, stateMachineInstance.isRunning()); + statement.setString(9, stateMachineInstance.getStatus().name()); + statement.setTimestamp(10, new Timestamp(stateMachineInstance.getGmtUpdated().getTime())); + } + } + + private static class StateMachineInstanceToStatementForUpdate implements ObjectToStatement { + @Override + public void toStatement(StateMachineInstance stateMachineInstance, PreparedStatement statement) + throws SQLException { + statement.setTimestamp(1, new Timestamp(stateMachineInstance.getGmtEnd().getTime())); + statement.setBytes(2, stateMachineInstance.getSerializedException() != null ? (byte[]) stateMachineInstance + .getSerializedException() : null); + statement.setObject(3, stateMachineInstance.getSerializedEndParams()); + statement.setString(4, stateMachineInstance.getStatus().name()); + statement.setString(5, + stateMachineInstance.getCompensationStatus() != null ? stateMachineInstance.getCompensationStatus() + .name() : null); + statement.setBoolean(6, stateMachineInstance.isRunning()); + statement.setTimestamp(7, new Timestamp(System.currentTimeMillis())); + statement.setString(8, stateMachineInstance.getId()); + statement.setTimestamp(9, new Timestamp(stateMachineInstance.getGmtUpdated().getTime())); + } + } + + private static class StateInstanceToStatementForInsert implements ObjectToStatement { + @Override + public void toStatement(StateInstance stateInstance, PreparedStatement statement) throws SQLException { + statement.setString(1, stateInstance.getId()); + statement.setString(2, stateInstance.getMachineInstanceId()); + statement.setString(3, stateInstance.getName()); + statement.setString(4, stateInstance.getType()); + statement.setTimestamp(5, new Timestamp(stateInstance.getGmtStarted().getTime())); + statement.setString(6, stateInstance.getServiceName()); + statement.setString(7, stateInstance.getServiceMethod()); + statement.setString(8, stateInstance.getServiceType()); + statement.setBoolean(9, stateInstance.isForUpdate()); + statement.setObject(10, stateInstance.getSerializedInputParams()); + statement.setString(11, stateInstance.getStatus().name()); + statement.setString(12, stateInstance.getBusinessKey()); + statement.setString(13, stateInstance.getStateIdCompensatedFor()); + statement.setString(14, stateInstance.getStateIdRetriedFor()); + statement.setTimestamp(15, new Timestamp(stateInstance.getGmtUpdated().getTime())); + } + } + + private static class StateInstanceToStatementForUpdate implements ObjectToStatement { + @Override + public void toStatement(StateInstance stateInstance, PreparedStatement statement) throws SQLException { + statement.setTimestamp(1, new Timestamp(stateInstance.getGmtEnd().getTime())); + statement.setBytes(2, + stateInstance.getException() != null ? (byte[]) stateInstance.getSerializedException() : null); + statement.setString(3, stateInstance.getStatus().name()); + statement.setObject(4, stateInstance.getSerializedOutputParams()); + statement.setTimestamp(5, new Timestamp(stateInstance.getGmtEnd().getTime())); + statement.setString(6, stateInstance.getId()); + statement.setString(7, stateInstance.getMachineInstanceId()); + } + } + + private static class ResultSetToStateMachineInstance implements ResultSetToObject { + @Override + public StateMachineInstance toObject(ResultSet resultSet) throws SQLException { + StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl(); + stateMachineInstance.setId(resultSet.getString("id")); + stateMachineInstance.setMachineId(resultSet.getString("machine_id")); + stateMachineInstance.setTenantId(resultSet.getString("tenant_id")); + stateMachineInstance.setParentId(resultSet.getString("parent_id")); + stateMachineInstance.setBusinessKey(resultSet.getString("business_key")); + stateMachineInstance.setGmtStarted(resultSet.getTimestamp("gmt_started")); + stateMachineInstance.setGmtEnd(resultSet.getTimestamp("gmt_end")); + stateMachineInstance.setStatus(ExecutionStatus.valueOf(resultSet.getString("status"))); + + String compensationStatusName = resultSet.getString("compensation_status"); + if (StringUtils.hasLength(compensationStatusName)) { + stateMachineInstance.setCompensationStatus(ExecutionStatus.valueOf(compensationStatusName)); + } + stateMachineInstance.setRunning(resultSet.getBoolean("is_running")); + stateMachineInstance.setGmtUpdated(resultSet.getTimestamp("gmt_updated")); + + if (resultSet.getMetaData().getColumnCount() > 11) { + stateMachineInstance.setSerializedStartParams(resultSet.getString("start_params")); + stateMachineInstance.setSerializedEndParams(resultSet.getString("end_params")); + stateMachineInstance.setSerializedException(resultSet.getBytes("excep")); + } + return stateMachineInstance; + } + } + + private static class ResultSetToStateInstance implements ResultSetToObject { + @Override + public StateInstance toObject(ResultSet resultSet) throws SQLException { + StateInstanceImpl stateInstance = new StateInstanceImpl(); + stateInstance.setId(resultSet.getString("id")); + stateInstance.setMachineInstanceId(resultSet.getString("machine_inst_id")); + stateInstance.setName(resultSet.getString("name")); + stateInstance.setType(resultSet.getString("type")); + stateInstance.setBusinessKey(resultSet.getString("business_key")); + stateInstance.setStatus(ExecutionStatus.valueOf(resultSet.getString("status"))); + stateInstance.setGmtStarted(resultSet.getTimestamp("gmt_started")); + stateInstance.setGmtEnd(resultSet.getTimestamp("gmt_end")); + stateInstance.setServiceName(resultSet.getString("service_name")); + stateInstance.setServiceMethod(resultSet.getString("service_method")); + stateInstance.setServiceType(resultSet.getString("service_type")); + stateInstance.setForUpdate(resultSet.getBoolean("is_for_update")); + stateInstance.setStateIdCompensatedFor(resultSet.getString("state_id_compensated_for")); + stateInstance.setStateIdRetriedFor(resultSet.getString("state_id_retried_for")); + stateInstance.setSerializedInputParams(resultSet.getString("input_params")); + stateInstance.setSerializedOutputParams(resultSet.getString("output_params")); + stateInstance.setSerializedException(resultSet.getBytes("excep")); + + return stateInstance; + } + } +} diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbStateLangStore.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbStateLangStore.java new file mode 100644 index 0000000..086edfd --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/DbStateLangStore.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store.db; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.List; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.store.StateLangStore; +import io.seata.saga.statelang.domain.RecoverStrategy; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachine.Status; +import io.seata.saga.statelang.domain.impl.StateMachineImpl; + +/** + * State language definition store in DB + * + * @author lorne.cl + */ +public class DbStateLangStore extends AbstractStore implements StateLangStore { + + private static final ResultSetToStateMachine RESULT_SET_TO_STATE_MACHINE = new ResultSetToStateMachine(); + + private static final StateMachineToStatement STATE_MACHINE_TO_STATEMENT = new StateMachineToStatement(); + + private StateLangStoreSqls stateLangStoreSqls; + + @Override + public StateMachine getStateMachineById(String stateMachineId) { + return selectOne(stateLangStoreSqls.getGetStateMachineByIdSql(dbType), RESULT_SET_TO_STATE_MACHINE, + stateMachineId); + } + + @Override + public StateMachine getLastVersionStateMachine(String stateMachineName, String tenantId) { + List list = selectList(stateLangStoreSqls.getQueryStateMachinesByNameAndTenantSql(dbType), + RESULT_SET_TO_STATE_MACHINE, stateMachineName, tenantId); + if (CollectionUtils.isNotEmpty(list)) { + return list.get(0); + } + return null; + } + + @Override + public boolean storeStateMachine(StateMachine stateMachine) { + return executeUpdate(stateLangStoreSqls.getInsertStateMachineSql(dbType), STATE_MACHINE_TO_STATEMENT, + stateMachine) > 0; + } + + @Override + public void setTablePrefix(String tablePrefix) { + super.setTablePrefix(tablePrefix); + this.stateLangStoreSqls = new StateLangStoreSqls(tablePrefix); + } + + private static class ResultSetToStateMachine implements ResultSetToObject { + @Override + public StateMachine toObject(ResultSet resultSet) throws SQLException { + StateMachineImpl stateMachine = new StateMachineImpl(); + stateMachine.setId(resultSet.getString("id")); + stateMachine.setName(resultSet.getString("name")); + stateMachine.setComment(resultSet.getString("comment_")); + stateMachine.setVersion(resultSet.getString("ver")); + stateMachine.setAppName(resultSet.getString("app_name")); + stateMachine.setContent(resultSet.getString("content")); + stateMachine.setGmtCreate(resultSet.getTimestamp("gmt_create")); + stateMachine.setType(resultSet.getString("type")); + String recoverStrategy = resultSet.getString("recover_strategy"); + if (StringUtils.isNotBlank(recoverStrategy)) { + stateMachine.setRecoverStrategy(RecoverStrategy.valueOf(recoverStrategy)); + } + stateMachine.setTenantId(resultSet.getString("tenant_id")); + stateMachine.setStatus(Status.valueOf(resultSet.getString("status"))); + return stateMachine; + } + } + + private static class StateMachineToStatement implements ObjectToStatement { + @Override + public void toStatement(StateMachine stateMachine, PreparedStatement statement) throws SQLException { + statement.setString(1, stateMachine.getId()); + statement.setString(2, stateMachine.getTenantId()); + statement.setString(3, stateMachine.getAppName()); + statement.setString(4, stateMachine.getName()); + statement.setString(5, stateMachine.getStatus().name()); + statement.setTimestamp(6, new Timestamp(stateMachine.getGmtCreate().getTime())); + statement.setString(7, stateMachine.getVersion()); + statement.setString(8, stateMachine.getType()); + statement.setString(9, stateMachine.getContent()); + statement.setString(10, stateMachine.getRecoverStrategy() != null ? stateMachine.getRecoverStrategy().name() : null); + statement.setString(11, stateMachine.getComment()); + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLangStoreSqls.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLangStoreSqls.java new file mode 100644 index 0000000..ea453f7 --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLangStoreSqls.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store.db; + +/** + * StateLang store sqls + * + * @author lorne.cl + */ +public class StateLangStoreSqls { + + private static final String STATE_MACHINE_FIELDS + = "id, tenant_id, app_name, name, status, gmt_create, ver, type, content, recover_strategy, comment_"; + + private static final String GET_STATE_MACHINE_BY_ID_SQL = "SELECT " + STATE_MACHINE_FIELDS + + " FROM ${TABLE_PREFIX}state_machine_def WHERE id = ?"; + + private static final String QUERY_STATE_MACHINES_BY_NAME_AND_TENANT_SQL = "SELECT " + STATE_MACHINE_FIELDS + + " FROM ${TABLE_PREFIX}state_machine_def WHERE name = ? AND tenant_id = ? ORDER BY gmt_create DESC"; + + private static final String INSERT_STATE_MACHINE_SQL = "INSERT INTO ${TABLE_PREFIX}state_machine_def (" + + STATE_MACHINE_FIELDS + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String TABLE_PREFIX_REGEX = "\\$\\{TABLE_PREFIX}"; + + private String tablePrefix; + + private String getGetStateMachineByIdSql; + private String queryStateMachinesByNameAndTenantSql; + private String insertStateMachineSql; + + public StateLangStoreSqls(String tablePrefix) { + this.tablePrefix = tablePrefix; + init(); + } + + private void init() { + getGetStateMachineByIdSql = GET_STATE_MACHINE_BY_ID_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + queryStateMachinesByNameAndTenantSql = QUERY_STATE_MACHINES_BY_NAME_AND_TENANT_SQL.replaceAll( + TABLE_PREFIX_REGEX, tablePrefix); + insertStateMachineSql = INSERT_STATE_MACHINE_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + } + + public String getGetStateMachineByIdSql(String dbType) { + return getGetStateMachineByIdSql; + } + + public String getQueryStateMachinesByNameAndTenantSql(String dbType) { + return queryStateMachinesByNameAndTenantSql; + } + + public String getInsertStateMachineSql(String dbType) { + return insertStateMachineSql; + } + + public String getTablePrefix() { + return tablePrefix; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLogStoreSqls.java b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLogStoreSqls.java new file mode 100644 index 0000000..3c237ae --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/java/io/seata/saga/engine/store/db/StateLogStoreSqls.java @@ -0,0 +1,192 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store.db; + +/** + * State log store sqls + * + * @author lorne.cl + */ +public class StateLogStoreSqls { + + /** + * machine instance + **/ + private static final String STATE_MACHINE_INSTANCE_FIELDS + = "id, machine_id, tenant_id, parent_id, business_key, gmt_started, gmt_end, status, compensation_status, " + + "is_running, gmt_updated, start_params, end_params, excep"; + + private static final String STATE_MACHINE_INSTANCE_FIELDS_WITHOUT_PARAMS + = "id, machine_id, tenant_id, parent_id, business_key, gmt_started, gmt_end, status, compensation_status, " + + "is_running, gmt_updated"; + + private static final String RECORD_STATE_MACHINE_STARTED_SQL = "INSERT INTO ${TABLE_PREFIX}state_machine_inst\n" + + "(id, machine_id, tenant_id, parent_id, gmt_started, business_key, start_params, is_running, status, " + + "gmt_updated)\n" + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String RECORD_STATE_MACHINE_FINISHED_SQL + = "UPDATE ${TABLE_PREFIX}state_machine_inst SET gmt_end = ?, excep = ?, end_params = ?,status = ?, " + + "compensation_status = ?, is_running = ?, gmt_updated = ? WHERE id = ? and gmt_updated = ?"; + + private static final String UPDATE_STATE_MACHINE_RUNNING_STATUS_SQL = + "UPDATE ${TABLE_PREFIX}state_machine_inst SET\n" + + "is_running = ?, gmt_updated = ? where id = ? and gmt_updated = ?"; + + private static final String GET_STATE_MACHINE_INSTANCE_BY_ID_SQL = "SELECT " + STATE_MACHINE_INSTANCE_FIELDS + + " FROM ${TABLE_PREFIX}state_machine_inst WHERE id = ?"; + + private static final String GET_STATE_MACHINE_INSTANCE_BY_BUSINESS_KEY_SQL = "SELECT " + + STATE_MACHINE_INSTANCE_FIELDS + + " FROM ${TABLE_PREFIX}state_machine_inst WHERE business_key = ? AND tenant_id = ?"; + + private static final String QUERY_STATE_MACHINE_INSTANCES_BY_PARENT_ID_SQL = "SELECT " + + STATE_MACHINE_INSTANCE_FIELDS_WITHOUT_PARAMS + + " FROM ${TABLE_PREFIX}state_machine_inst WHERE parent_id = ? ORDER BY gmt_started DESC"; + + /** + * state instance + **/ + private static final String STATE_INSTANCE_FIELDS + = "id, machine_inst_id, name, type, business_key, gmt_started, service_name, service_method, service_type, " + + "is_for_update, status, input_params, output_params, excep, gmt_end, state_id_compensated_for, " + + "state_id_retried_for"; + + private static final String RECORD_STATE_STARTED_SQL = + "INSERT INTO ${TABLE_PREFIX}state_inst (id, machine_inst_id, name, type," + + " gmt_started, service_name, service_method, service_type, is_for_update, input_params, status, " + + "business_key, state_id_compensated_for, state_id_retried_for, gmt_updated)\n" + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + private static final String RECORD_STATE_FINISHED_SQL = + "UPDATE ${TABLE_PREFIX}state_inst SET gmt_end = ?, excep = ?, status = ?, output_params = ?, gmt_updated = ? " + + "WHERE id = ? AND machine_inst_id = ?"; + + private static final String UPDATE_STATE_EXECUTION_STATUS_SQL + = "UPDATE ${TABLE_PREFIX}state_inst SET status = ?, gmt_updated = ? WHERE machine_inst_id = ? AND id = ?"; + + private static final String QUERY_STATE_INSTANCES_BY_MACHINE_INSTANCE_ID_SQL = "SELECT " + STATE_INSTANCE_FIELDS + + " FROM ${TABLE_PREFIX}state_inst WHERE machine_inst_id = ? ORDER BY gmt_started, ID ASC"; + + private static final String GET_STATE_INSTANCE_BY_ID_AND_MACHINE_INSTANCE_ID_SQL = "SELECT " + STATE_INSTANCE_FIELDS + + " FROM ${TABLE_PREFIX}state_inst WHERE machine_inst_id = ? AND id = ?"; + + private static final String TABLE_PREFIX_REGEX = "\\$\\{TABLE_PREFIX}"; + + private String tablePrefix; + + /** + * machine instance + **/ + private String recordStateMachineStartedSql; + + private String recordStateMachineFinishedSql; + + private String updateStateMachineRunningStatusSql; + + private String getStateMachineInstanceByIdSql; + + private String getStateMachineInstanceByBusinessKeySql; + + private String queryStateMachineInstancesByParentIdSql; + + /** + * state instance + **/ + private String recordStateStartedSql; + + private String recordStateFinishedSql; + + private String updateStateExecutionStatusSql; + + private String queryStateInstancesByMachineInstanceIdSql; + + private String getStateInstanceByIdAndMachineInstanceIdSql; + + public StateLogStoreSqls(String tablePrefix) { + this.tablePrefix = tablePrefix; + init(); + } + + private void init() { + recordStateMachineStartedSql = RECORD_STATE_MACHINE_STARTED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + recordStateMachineFinishedSql = RECORD_STATE_MACHINE_FINISHED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + updateStateMachineRunningStatusSql = UPDATE_STATE_MACHINE_RUNNING_STATUS_SQL.replaceAll(TABLE_PREFIX_REGEX, + tablePrefix); + getStateMachineInstanceByIdSql = GET_STATE_MACHINE_INSTANCE_BY_ID_SQL.replaceAll(TABLE_PREFIX_REGEX, + tablePrefix); + getStateMachineInstanceByBusinessKeySql = GET_STATE_MACHINE_INSTANCE_BY_BUSINESS_KEY_SQL.replaceAll( + TABLE_PREFIX_REGEX, tablePrefix); + queryStateMachineInstancesByParentIdSql = QUERY_STATE_MACHINE_INSTANCES_BY_PARENT_ID_SQL.replaceAll( + TABLE_PREFIX_REGEX, tablePrefix); + + recordStateStartedSql = RECORD_STATE_STARTED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + recordStateFinishedSql = RECORD_STATE_FINISHED_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + updateStateExecutionStatusSql = UPDATE_STATE_EXECUTION_STATUS_SQL.replaceAll(TABLE_PREFIX_REGEX, tablePrefix); + queryStateInstancesByMachineInstanceIdSql = QUERY_STATE_INSTANCES_BY_MACHINE_INSTANCE_ID_SQL.replaceAll( + TABLE_PREFIX_REGEX, tablePrefix); + getStateInstanceByIdAndMachineInstanceIdSql = GET_STATE_INSTANCE_BY_ID_AND_MACHINE_INSTANCE_ID_SQL.replaceAll( + TABLE_PREFIX_REGEX, tablePrefix); + } + + public String getRecordStateMachineStartedSql(String dbType) { + return recordStateMachineStartedSql; + } + + public String getRecordStateMachineFinishedSql(String dbType) { + return recordStateMachineFinishedSql; + } + + public String getUpdateStateMachineRunningStatusSql(String dbType) { + return updateStateMachineRunningStatusSql; + } + + public String getGetStateMachineInstanceByIdSql(String dbType) { + return getStateMachineInstanceByIdSql; + } + + public String getGetStateMachineInstanceByBusinessKeySql(String dbType) { + return getStateMachineInstanceByBusinessKeySql; + } + + public String getQueryStateMachineInstancesByParentIdSql(String dbType) { + return queryStateMachineInstancesByParentIdSql; + } + + public String getRecordStateStartedSql(String dbType) { + return recordStateStartedSql; + } + + public String getRecordStateFinishedSql(String dbType) { + return recordStateFinishedSql; + } + + public String getUpdateStateExecutionStatusSql(String dbType) { + return updateStateExecutionStatusSql; + } + + public String getQueryStateInstancesByMachineInstanceIdSql(String dbType) { + return queryStateInstancesByMachineInstanceIdSql; + } + + public String getGetStateInstanceByIdAndMachineInstanceIdSql(String dbType) { + return getStateInstanceByIdAndMachineInstanceIdSql; + } + + public String getTablePrefix() { + return tablePrefix; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor b/saga/seata-saga-engine-store/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor new file mode 100644 index 0000000..2bbf7dd --- /dev/null +++ b/saga/seata-saga-engine-store/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor @@ -0,0 +1 @@ +io.seata.saga.engine.pcext.interceptors.InSagaBranchHandlerInterceptor \ No newline at end of file diff --git a/saga/seata-saga-engine/pom.xml b/saga/seata-saga-engine/pom.xml new file mode 100644 index 0000000..d3ce423 --- /dev/null +++ b/saga/seata-saga-engine/pom.xml @@ -0,0 +1,55 @@ + + + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-engine ${project.version} + seata-saga-engine + + + + ${project.groupId} + seata-saga-statelang + ${project.version} + + + ${project.groupId} + seata-saga-processctrl + ${project.version} + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-expression + + + + \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/AsyncCallback.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/AsyncCallback.java new file mode 100644 index 0000000..e450e87 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/AsyncCallback.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine; + +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * Async Callback + * + * @author lorne.cl + */ +public interface AsyncCallback { + + /** + * on finished + * + * @param context + * @param stateMachineInstance + */ + void onFinished(ProcessContext context, StateMachineInstance stateMachineInstance); + + /** + * on error + * + * @param context + * @param stateMachineInstance + * @param exp + */ + void onError(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java new file mode 100644 index 0000000..6249cfe --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineConfig.java @@ -0,0 +1,171 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine; + +import java.util.concurrent.ThreadPoolExecutor; + +import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; +import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.invoker.ServiceInvokerManager; +import io.seata.saga.engine.repo.StateLogRepository; +import io.seata.saga.engine.repo.StateMachineRepository; +import io.seata.saga.engine.sequence.SeqGenerator; +import io.seata.saga.engine.store.StateLangStore; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.engine.strategy.StatusDecisionStrategy; +import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher; +import org.springframework.context.ApplicationContext; + +import javax.script.ScriptEngineManager; + +/** + * StateMachineConfig + * + * @author lorne.cl + */ +public interface StateMachineConfig { + + /** + * Gets state log store. + * + * @return the StateLogRepository + */ + StateLogRepository getStateLogRepository(); + + /** + * Gets get state log store. + * + * @return the get StateLogStore + */ + StateLogStore getStateLogStore(); + + /** + * Gets get state language definition store. + * + * @return the get StateLangStore + */ + StateLangStore getStateLangStore(); + + /** + * Gets get expression factory manager. + * + * @return the get expression factory manager + */ + ExpressionFactoryManager getExpressionFactoryManager(); + + /** + * Gets get evaluator factory manager. + * + * @return the get evaluator factory manager + */ + EvaluatorFactoryManager getEvaluatorFactoryManager(); + + /** + * Gets get charset. + * + * @return the get charset + */ + String getCharset(); + + /** + * Gets get default tenant id. + * + * @return the default tenant id + */ + String getDefaultTenantId(); + + /** + * Gets get state machine repository. + * + * @return the get state machine repository + */ + StateMachineRepository getStateMachineRepository(); + + /** + * Gets get status decision strategy. + * + * @return the get status decision strategy + */ + StatusDecisionStrategy getStatusDecisionStrategy(); + + /** + * Gets get seq generator. + * + * @return the get seq generator + */ + SeqGenerator getSeqGenerator(); + + /** + * Gets get process ctrl event publisher. + * + * @return the get process ctrl event publisher + */ + ProcessCtrlEventPublisher getProcessCtrlEventPublisher(); + + /** + * Gets get async process ctrl event publisher. + * + * @return the get async process ctrl event publisher + */ + ProcessCtrlEventPublisher getAsyncProcessCtrlEventPublisher(); + + /** + * Gets get application context. + * + * @return the get application context + */ + ApplicationContext getApplicationContext(); + + /** + * Gets get thread pool executor. + * + * @return the get thread pool executor + */ + ThreadPoolExecutor getThreadPoolExecutor(); + + /** + * Is enable async boolean. + * + * @return the boolean + */ + boolean isEnableAsync(); + + /** + * get ServiceInvokerManager + * + * @return + */ + ServiceInvokerManager getServiceInvokerManager(); + + /** + * get trans operation timeout + * @return + */ + int getTransOperationTimeout(); + + /** + * get service invoke timeout + * @return + */ + int getServiceInvokeTimeout(); + + /** + * get ScriptEngineManager + * + * @return + */ + ScriptEngineManager getScriptEngineManager(); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineEngine.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineEngine.java new file mode 100644 index 0000000..15f351f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/StateMachineEngine.java @@ -0,0 +1,163 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine; + +import java.util.Map; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * State machine engine + * + * @author lorne.cl + */ +public interface StateMachineEngine { + + /** + * start a state machine instance + * + * @param stateMachineName + * @param tenantId + * @param startParams + * @return + * @throws EngineExecutionException + */ + StateMachineInstance start(String stateMachineName, String tenantId, Map startParams) + throws EngineExecutionException; + + /** + * start a state machine instance with businessKey + * + * @param stateMachineName + * @param tenantId + * @param businessKey + * @param startParams + * @return + * @throws EngineExecutionException + */ + StateMachineInstance startWithBusinessKey(String stateMachineName, String tenantId, String businessKey, + Map startParams) throws EngineExecutionException; + + /** + * start a state machine instance asynchronously + * + * @param stateMachineName + * @param tenantId + * @param startParams + * @param callback + * @return + * @throws EngineExecutionException + */ + StateMachineInstance startAsync(String stateMachineName, String tenantId, Map startParams, + AsyncCallback callback) throws EngineExecutionException; + + /** + * start a state machine instance asynchronously with businessKey + * + * @param stateMachineName + * @param tenantId + * @param businessKey + * @param startParams + * @param callback + * @return + * @throws EngineExecutionException + */ + StateMachineInstance startWithBusinessKeyAsync(String stateMachineName, String tenantId, String businessKey, + Map startParams, AsyncCallback callback) + throws EngineExecutionException; + + /** + * forward restart a failed state machine instance + * + * @param stateMachineInstId + * @param replaceParams + * @return + * @throws ForwardInvalidException + */ + StateMachineInstance forward(String stateMachineInstId, Map replaceParams) + throws ForwardInvalidException; + + /** + * forward restart a failed state machine instance asynchronously + * + * @param stateMachineInstId + * @param replaceParams + * @param callback + * @return + * @throws ForwardInvalidException + */ + StateMachineInstance forwardAsync(String stateMachineInstId, Map replaceParams, + AsyncCallback callback) throws ForwardInvalidException; + + /** + * compensate a state machine instance + * + * @param stateMachineInstId + * @param replaceParams + * @return + * @throws EngineExecutionException + */ + StateMachineInstance compensate(String stateMachineInstId, Map replaceParams) + throws EngineExecutionException; + + /** + * compensate a state machine instance asynchronously + * + * @param stateMachineInstId + * @param replaceParams + * @param callback + * @return + * @throws EngineExecutionException + */ + StateMachineInstance compensateAsync(String stateMachineInstId, Map replaceParams, + AsyncCallback callback) throws EngineExecutionException; + + /** + * skip current failed state instance and forward restart state machine instance + * + * @param stateMachineInstId + * @return + * @throws EngineExecutionException + */ + StateMachineInstance skipAndForward(String stateMachineInstId, Map replaceParams) throws EngineExecutionException; + + /** + * skip current failed state instance and forward restart state machine instance asynchronously + * + * @param stateMachineInstId + * @param callback + * @return + * @throws EngineExecutionException + */ + StateMachineInstance skipAndForwardAsync(String stateMachineInstId, AsyncCallback callback) + throws EngineExecutionException; + + /** + * get state machine configurations + * + * @return + */ + StateMachineConfig getStateMachineConfig(); + + /** + * Reload StateMachine Instance + * @param instId + * @return + */ + StateMachineInstance reloadStateMachineInstance(String instId); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java new file mode 100644 index 0000000..8fc530a --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/Evaluator.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation; + +import java.util.Map; + +/** + * Evaluator + * + * @author lorne.cl + */ +public interface Evaluator { + + boolean evaluate(Map variables); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java new file mode 100644 index 0000000..df4e647 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation; + +/** + * Evaluator Factory + * + * @author lorne.cl + * @see Evaluator + */ +public interface EvaluatorFactory { + + /** + * create evaluator + * + * @param expressionString + * @return + */ + Evaluator createEvaluator(String expressionString); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java new file mode 100644 index 0000000..3e2e6ae --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/EvaluatorFactoryManager.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.util.StringUtils; + +/** + * Evaluator Factory Manager + * + * @author lorne.cl + * @see EvaluatorFactory + * @see Evaluator + */ +public class EvaluatorFactoryManager { + + public static final String EVALUATOR_TYPE_DEFAULT = "Default"; + + private Map evaluatorFactoryMap = new ConcurrentHashMap<>(); + + public EvaluatorFactory getEvaluatorFactory(String type) { + + if (StringUtils.isBlank(type)) { + type = EVALUATOR_TYPE_DEFAULT; + } + return this.evaluatorFactoryMap.get(type); + } + + public Map getEvaluatorFactoryMap() { + return evaluatorFactoryMap; + } + + public void setEvaluatorFactoryMap(Map evaluatorFactoryMap) { + this.evaluatorFactoryMap.putAll(evaluatorFactoryMap); + } + + public void putEvaluatorFactory(String type, EvaluatorFactory factory) { + this.evaluatorFactoryMap.put(type, factory); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java new file mode 100644 index 0000000..15c9e1c --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluator.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation.exception; + +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.statelang.domain.DomainConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * Exception match evaluator + * + * @author lorne.cl + */ +public class ExceptionMatchEvaluator implements Evaluator { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionMatchEvaluator.class); + + private String exceptionString; + + private Class exceptionClass; + + private String rootObjectName = DomainConstants.VAR_NAME_CURRENT_EXCEPTION; + + @Override + public boolean evaluate(Map variables) { + + Object eObj = variables.get(getRootObjectName()); + if (eObj != null && (eObj instanceof Exception) && StringUtils.hasText(exceptionString)) { + + Exception e = (Exception)eObj; + + String exceptionClassName = e.getClass().getName(); + if (exceptionClassName.equals(exceptionString)) { + return true; + } + try { + if (exceptionClass.isAssignableFrom(e.getClass())) { + return true; + } + } catch (Exception e1) { + LOGGER.error("Exception Match failed. expression[{}]", exceptionString, e1); + } + } + + return false; + } + + public String getExceptionString() { + return exceptionString; + } + + @SuppressWarnings("unchecked") + public void setExceptionString(String exceptionString) { + this.exceptionString = exceptionString; + try { + this.exceptionClass = (Class)Class.forName(exceptionString); + } catch (ClassNotFoundException e) { + throw new EngineExecutionException(e, exceptionString + " is not a Exception Class", + FrameworkErrorCode.NotExceptionClass); + } + } + + public Class getExceptionClass() { + return exceptionClass; + } + + public void setExceptionClass(Class exceptionClass) { + this.exceptionClass = exceptionClass; + this.exceptionString = exceptionClass.getName(); + } + + public String getRootObjectName() { + return rootObjectName; + } + + public void setRootObjectName(String rootObjectName) { + this.rootObjectName = rootObjectName; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java new file mode 100644 index 0000000..bcb9f35 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/exception/ExceptionMatchEvaluatorFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation.exception; + +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.evaluation.EvaluatorFactory; +import io.seata.saga.statelang.domain.DomainConstants; + +/** + * Exception match evaluator factory + * + * @author lorne.cl + */ +public class ExceptionMatchEvaluatorFactory implements EvaluatorFactory { + + @Override + public Evaluator createEvaluator(String expressionString) { + + ExceptionMatchEvaluator evaluator = new ExceptionMatchEvaluator(); + evaluator.setExceptionString(expressionString); + evaluator.setRootObjectName(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + return evaluator; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java new file mode 100644 index 0000000..b35792b --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluator.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation.expression; + +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.statelang.domain.DomainConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * Expression evaluator + * + * @author lorne.cl + */ +public class ExpressionEvaluator implements Evaluator { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionEvaluator.class); + + private Expression expression; + + /** + * If it is empty, use variables as the root variable, otherwise take rootObjectName as the root. + */ + private String rootObjectName = DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT; + + @Override + public boolean evaluate(Map variables) { + + Object rootObject; + if (StringUtils.hasText(this.rootObjectName)) { + rootObject = variables.get(this.rootObjectName); + } else { + rootObject = variables; + } + + Object result; + try { + result = expression.getValue(rootObject); + } catch (Exception e) { + result = Boolean.FALSE; + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Expression [{}] execute failed, and it will return false by default. variables:{}", + expression.getExpressionString(), variables, e); + } + } + + if (result == null) { + throw new EngineExecutionException("Evaluation returns null", FrameworkErrorCode.EvaluationReturnsNull); + } + if (!(result instanceof Boolean)) { + throw new EngineExecutionException( + "Evaluation returns non-Boolean: " + result + " (" + result.getClass().getName() + ")", + FrameworkErrorCode.EvaluationReturnsNonBoolean); + } + return (Boolean)result; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public String getRootObjectName() { + return rootObjectName; + } + + public void setRootObjectName(String rootObjectName) { + this.rootObjectName = rootObjectName; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java new file mode 100644 index 0000000..8dd4761 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/evaluation/expression/ExpressionEvaluatorFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.evaluation.expression; + +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.evaluation.EvaluatorFactory; +import io.seata.saga.engine.expression.ExpressionFactory; + +/** + * Expression evaluator factory + * + * @author lorne.cl + */ +public class ExpressionEvaluatorFactory implements EvaluatorFactory { + + private ExpressionFactory expressionFactory; + + @Override + public Evaluator createEvaluator(String expressionString) { + + ExpressionEvaluator evaluator = new ExpressionEvaluator(); + evaluator.setExpression(this.expressionFactory.createExpression(expressionString)); + return evaluator; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public void setExpressionFactory(ExpressionFactory expressionFactory) { + this.expressionFactory = expressionFactory; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/EngineExecutionException.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/EngineExecutionException.java new file mode 100644 index 0000000..48f1a7b --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/EngineExecutionException.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.exception; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; + +/** + * StateMachineEngine Execution Exception + * + * @author lorne.cl + */ +public class EngineExecutionException extends FrameworkException { + + private String stateName; + private String stateMachineName; + private String stateMachineInstanceId; + private String stateInstanceId; + + public EngineExecutionException() { + } + + public EngineExecutionException(FrameworkErrorCode err) { + super(err); + } + + public EngineExecutionException(String msg) { + super(msg); + } + + public EngineExecutionException(String msg, FrameworkErrorCode errCode) { + super(msg, errCode); + } + + public EngineExecutionException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(cause, msg, errCode); + } + + public EngineExecutionException(Throwable th) { + super(th); + } + + public EngineExecutionException(Throwable th, String msg) { + super(th, msg); + } + + public String getStateName() { + return stateName; + } + + public void setStateName(String stateName) { + this.stateName = stateName; + } + + public String getStateMachineName() { + return stateMachineName; + } + + public void setStateMachineName(String stateMachineName) { + this.stateMachineName = stateMachineName; + } + + public String getStateMachineInstanceId() { + return stateMachineInstanceId; + } + + public void setStateMachineInstanceId(String stateMachineInstanceId) { + this.stateMachineInstanceId = stateMachineInstanceId; + } + + public String getStateInstanceId() { + return stateInstanceId; + } + + public void setStateInstanceId(String stateInstanceId) { + this.stateInstanceId = stateInstanceId; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/ForwardInvalidException.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/ForwardInvalidException.java new file mode 100644 index 0000000..21aa6f1 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/exception/ForwardInvalidException.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.exception; + +import io.seata.common.exception.FrameworkErrorCode; + +/** + * Forward operation invalid exception + * + * @author lorne.cl + */ +public class ForwardInvalidException extends EngineExecutionException { + + public ForwardInvalidException() { + } + + public ForwardInvalidException(FrameworkErrorCode err) { + super(err); + } + + public ForwardInvalidException(String msg) { + super(msg); + } + + public ForwardInvalidException(String msg, FrameworkErrorCode errCode) { + super(msg, errCode); + } + + public ForwardInvalidException(Throwable cause, String msg, FrameworkErrorCode errCode) { + super(cause, msg, errCode); + } + + public ForwardInvalidException(Throwable th) { + super(th); + } + + public ForwardInvalidException(Throwable th, String msg) { + super(th, msg); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/Expression.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/Expression.java new file mode 100644 index 0000000..5194328 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/Expression.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression; + +/** + * Expression + * + * @author lorne.cl + */ +public interface Expression { + + /** + * Gets get value. + * + * @param elContext the el context + * @return the get value + */ + Object getValue(Object elContext); + + /** + * Sets set value. + * + * @param value the value + * @param elContext the el context + */ + void setValue(Object value, Object elContext); + + /** + * Gets get expression string. + * + * @return the get expression string + */ + String getExpressionString(); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactory.java new file mode 100644 index 0000000..8fe16d4 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression; + +/** + * Expression Factory + * + * @author lorne.cl + */ +public interface ExpressionFactory { + + /** + * create expression + * + * @param expression + * @return + */ + Expression createExpression(String expression); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactoryManager.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactoryManager.java new file mode 100644 index 0000000..05c980a --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/ExpressionFactoryManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.util.StringUtils; + +/** + * Expression factory manager + * + * @author lorne.cl + */ +public class ExpressionFactoryManager { + + public static final String DEFAULT_EXPRESSION_TYPE = "Default"; + + private Map expressionFactoryMap = new ConcurrentHashMap<>(); + + public ExpressionFactory getExpressionFactory(String expressionType) { + if (StringUtils.isBlank(expressionType)) { + expressionType = DEFAULT_EXPRESSION_TYPE; + } + return expressionFactoryMap.get(expressionType); + } + + public void setExpressionFactoryMap(Map expressionFactoryMap) { + + this.expressionFactoryMap.putAll(expressionFactoryMap); + } + + public void putExpressionFactory(String type, ExpressionFactory factory) { + this.expressionFactoryMap.put(type, factory); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpression.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpression.java new file mode 100644 index 0000000..0970877 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpression.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression.seq; + +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.sequence.SeqGenerator; + +/** + * Generate sequence expression + * + * @author lorne.cl + */ +public class SequenceExpression implements Expression { + + private SeqGenerator seqGenerator; + private String entity; + private String rule; + + @Override + public Object getValue(Object elContext) { + return seqGenerator.generate(entity, rule, null); + } + + @Override + public void setValue(Object value, Object elContext) { + + } + + @Override + public String getExpressionString() { + return this.entity + "|" + this.rule; + } + + public SeqGenerator getSeqGenerator() { + return seqGenerator; + } + + public void setSeqGenerator(SeqGenerator seqGenerator) { + this.seqGenerator = seqGenerator; + } + + public String getEntity() { + return entity; + } + + public void setEntity(String entity) { + this.entity = entity; + } + + public String getRule() { + return rule; + } + + public void setRule(String rule) { + this.rule = rule; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpressionFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpressionFactory.java new file mode 100644 index 0000000..cbd2374 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/seq/SequenceExpressionFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression.seq; + +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionFactory; +import io.seata.saga.engine.sequence.SeqGenerator; +import org.springframework.util.StringUtils; + +/** + * Sequence expression factory + * + * @author lorne.cl + */ +public class SequenceExpressionFactory implements ExpressionFactory { + + private SeqGenerator seqGenerator; + + @Override + public Expression createExpression(String expressionString) { + + SequenceExpression sequenceExpression = new SequenceExpression(); + sequenceExpression.setSeqGenerator(this.seqGenerator); + if (StringUtils.hasLength(expressionString)) { + String[] strings = expressionString.split("\\|"); + if (strings.length >= 2) { + sequenceExpression.setEntity(strings[0]); + sequenceExpression.setRule(strings[1]); + } + } + return sequenceExpression; + } + + public SeqGenerator getSeqGenerator() { + return seqGenerator; + } + + public void setSeqGenerator(SeqGenerator seqGenerator) { + this.seqGenerator = seqGenerator; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpression.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpression.java new file mode 100644 index 0000000..60d44ce --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpression.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression.spel; + +import io.seata.saga.engine.expression.Expression; + +/** + * Expression base on Spring EL + * + * @author lorne.cl + */ +public class SpringELExpression implements Expression { + + private org.springframework.expression.Expression expression; + + public SpringELExpression(org.springframework.expression.Expression expression) { + this.expression = expression; + } + + @Override + public Object getValue(Object elContext) { + return expression.getValue(elContext); + } + + @Override + public void setValue(Object value, Object elContext) { + expression.setValue(elContext, value); + } + + @Override + public String getExpressionString() { + return expression.getExpressionString(); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpressionFactory.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpressionFactory.java new file mode 100644 index 0000000..69b0ab4 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/expression/spel/SpringELExpressionFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.expression.spel; + +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.expression.AccessException; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * SpringELExpression factory + * + * @author lorne.cl + */ +public class SpringELExpressionFactory implements ExpressionFactory, ApplicationContextAware { + + ExpressionParser parser = new SpelExpressionParser(); + ApplicationContext applicationContext; + + @Override + public Expression createExpression(String expression) { + org.springframework.expression.Expression defaultExpression = parser.parseExpression(expression); + EvaluationContext evaluationContext = ((SpelExpression)defaultExpression).getEvaluationContext(); + ((StandardEvaluationContext)evaluationContext).setBeanResolver(new AppContextBeanResolver()); + return new SpringELExpression(defaultExpression); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + private class AppContextBeanResolver implements BeanResolver { + + @Override + public Object resolve(EvaluationContext context, String beanName) throws AccessException { + return applicationContext.getBean(beanName); + } + + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java new file mode 100644 index 0000000..6e47cdd --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/DefaultStateMachineConfig.java @@ -0,0 +1,501 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.impl; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; +import io.seata.saga.engine.evaluation.exception.ExceptionMatchEvaluatorFactory; +import io.seata.saga.engine.evaluation.expression.ExpressionEvaluatorFactory; +import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.seq.SequenceExpressionFactory; +import io.seata.saga.engine.expression.spel.SpringELExpressionFactory; +import io.seata.saga.engine.invoker.ServiceInvokerManager; +import io.seata.saga.engine.invoker.impl.SpringBeanServiceInvoker; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.InterceptableStateRouter; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateMachineProcessHandler; +import io.seata.saga.engine.pcext.StateMachineProcessRouter; +import io.seata.saga.engine.pcext.StateRouter; +import io.seata.saga.engine.pcext.StateRouterInterceptor; +import io.seata.saga.engine.repo.StateLogRepository; +import io.seata.saga.engine.repo.StateMachineRepository; +import io.seata.saga.engine.repo.impl.StateLogRepositoryImpl; +import io.seata.saga.engine.repo.impl.StateMachineRepositoryImpl; +import io.seata.saga.engine.sequence.SeqGenerator; +import io.seata.saga.engine.sequence.SpringJvmUUIDSeqGenerator; +import io.seata.saga.engine.store.StateLangStore; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.engine.strategy.StatusDecisionStrategy; +import io.seata.saga.engine.strategy.impl.DefaultStatusDecisionStrategy; +import io.seata.saga.proctrl.ProcessRouter; +import io.seata.saga.proctrl.ProcessType; +import io.seata.saga.proctrl.eventing.impl.AsyncEventBus; +import io.seata.saga.proctrl.eventing.impl.DirectEventBus; +import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventConsumer; +import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher; +import io.seata.saga.proctrl.handler.DefaultRouterHandler; +import io.seata.saga.proctrl.handler.ProcessHandler; +import io.seata.saga.proctrl.handler.RouterHandler; +import io.seata.saga.proctrl.impl.ProcessControllerImpl; +import io.seata.saga.proctrl.process.impl.CustomizeBusinessProcessor; +import io.seata.saga.statelang.domain.DomainConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.io.Resource; + +import javax.script.ScriptEngineManager; + +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE; +import static io.seata.common.DefaultValues.DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE; +import static io.seata.common.DefaultValues.DEFAULT_SAGA_JSON_PARSER; + +/** + * Default state machine configuration + * + * @author lorne.cl + */ +public class DefaultStateMachineConfig implements StateMachineConfig, ApplicationContextAware, InitializingBean { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStateMachineConfig.class); + + private static final int DEFAULT_TRANS_OPER_TIMEOUT = 60000 * 30; + private static final int DEFAULT_SERVICE_INVOKE_TIMEOUT = 60000 * 5; + + private int transOperationTimeout = DEFAULT_TRANS_OPER_TIMEOUT; + private int serviceInvokeTimeout = DEFAULT_SERVICE_INVOKE_TIMEOUT; + + private StateLogRepository stateLogRepository; + private StateLogStore stateLogStore; + private StateLangStore stateLangStore; + private ExpressionFactoryManager expressionFactoryManager; + private EvaluatorFactoryManager evaluatorFactoryManager; + private StateMachineRepository stateMachineRepository; + private StatusDecisionStrategy statusDecisionStrategy; + private SeqGenerator seqGenerator; + + private ProcessCtrlEventPublisher syncProcessCtrlEventPublisher; + private ProcessCtrlEventPublisher asyncProcessCtrlEventPublisher; + private ApplicationContext applicationContext; + private ThreadPoolExecutor threadPoolExecutor; + private boolean enableAsync; + private ServiceInvokerManager serviceInvokerManager; + + private Resource[] resources = new Resource[0]; + private String charset = "UTF-8"; + private String defaultTenantId = "000001"; + private ScriptEngineManager scriptEngineManager; + private String sagaJsonParser = DEFAULT_SAGA_JSON_PARSER; + private boolean sagaRetryPersistModeUpdate = DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE; + private boolean sagaCompensatePersistModeUpdate = DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE; + + protected void init() throws Exception { + + if (expressionFactoryManager == null) { + expressionFactoryManager = new ExpressionFactoryManager(); + + SpringELExpressionFactory springELExpressionFactory = new SpringELExpressionFactory(); + springELExpressionFactory.setApplicationContext(getApplicationContext()); + expressionFactoryManager.putExpressionFactory(ExpressionFactoryManager.DEFAULT_EXPRESSION_TYPE, + springELExpressionFactory); + + SequenceExpressionFactory sequenceExpressionFactory = new SequenceExpressionFactory(); + sequenceExpressionFactory.setSeqGenerator(getSeqGenerator()); + expressionFactoryManager.putExpressionFactory(DomainConstants.EXPRESSION_TYPE_SEQUENCE, + sequenceExpressionFactory); + } + + if (evaluatorFactoryManager == null) { + evaluatorFactoryManager = new EvaluatorFactoryManager(); + + ExpressionEvaluatorFactory expressionEvaluatorFactory = new ExpressionEvaluatorFactory(); + expressionEvaluatorFactory.setExpressionFactory( + expressionFactoryManager.getExpressionFactory(ExpressionFactoryManager.DEFAULT_EXPRESSION_TYPE)); + evaluatorFactoryManager.putEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT, + expressionEvaluatorFactory); + + evaluatorFactoryManager.putEvaluatorFactory(DomainConstants.EVALUATOR_TYPE_EXCEPTION, + new ExceptionMatchEvaluatorFactory()); + } + + if (stateMachineRepository == null) { + StateMachineRepositoryImpl stateMachineRepository = new StateMachineRepositoryImpl(); + stateMachineRepository.setCharset(charset); + stateMachineRepository.setSeqGenerator(seqGenerator); + stateMachineRepository.setStateLangStore(stateLangStore); + stateMachineRepository.setDefaultTenantId(defaultTenantId); + stateMachineRepository.setJsonParserName(sagaJsonParser); + if (resources != null) { + try { + stateMachineRepository.registryByResources(resources, defaultTenantId); + } catch (IOException e) { + LOGGER.error("Load State Language Resources failed.", e); + } + } + this.stateMachineRepository = stateMachineRepository; + } + + if (stateLogRepository == null) { + StateLogRepositoryImpl stateLogRepositoryImpl = new StateLogRepositoryImpl(); + stateLogRepositoryImpl.setStateLogStore(stateLogStore); + this.stateLogRepository = stateLogRepositoryImpl; + } + + if (statusDecisionStrategy == null) { + statusDecisionStrategy = new DefaultStatusDecisionStrategy(); + } + + if (syncProcessCtrlEventPublisher == null) { + ProcessCtrlEventPublisher syncEventPublisher = new ProcessCtrlEventPublisher(); + + ProcessControllerImpl processorController = createProcessorController(syncEventPublisher); + + ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer(); + processCtrlEventConsumer.setProcessController(processorController); + + DirectEventBus directEventBus = new DirectEventBus(); + syncEventPublisher.setEventBus(directEventBus); + + directEventBus.registerEventConsumer(processCtrlEventConsumer); + + syncProcessCtrlEventPublisher = syncEventPublisher; + } + + if (enableAsync && asyncProcessCtrlEventPublisher == null) { + ProcessCtrlEventPublisher asyncEventPublisher = new ProcessCtrlEventPublisher(); + + ProcessControllerImpl processorController = createProcessorController(asyncEventPublisher); + + ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer(); + processCtrlEventConsumer.setProcessController(processorController); + + AsyncEventBus asyncEventBus = new AsyncEventBus(); + asyncEventBus.setThreadPoolExecutor(getThreadPoolExecutor()); + asyncEventPublisher.setEventBus(asyncEventBus); + + asyncEventBus.registerEventConsumer(processCtrlEventConsumer); + + asyncProcessCtrlEventPublisher = asyncEventPublisher; + } + + if (this.serviceInvokerManager == null) { + this.serviceInvokerManager = new ServiceInvokerManager(); + + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + springBeanServiceInvoker.setApplicationContext(getApplicationContext()); + springBeanServiceInvoker.setThreadPoolExecutor(threadPoolExecutor); + springBeanServiceInvoker.setSagaJsonParser(getSagaJsonParser()); + this.serviceInvokerManager.putServiceInvoker(DomainConstants.SERVICE_TYPE_SPRING_BEAN, + springBeanServiceInvoker); + } + + if (this.scriptEngineManager == null) { + this.scriptEngineManager = new ScriptEngineManager(); + } + } + + protected ProcessControllerImpl createProcessorController(ProcessCtrlEventPublisher eventPublisher) throws Exception { + + StateMachineProcessRouter stateMachineProcessRouter = new StateMachineProcessRouter(); + stateMachineProcessRouter.initDefaultStateRouters(); + loadStateRouterInterceptors(stateMachineProcessRouter.getStateRouters()); + + StateMachineProcessHandler stateMachineProcessHandler = new StateMachineProcessHandler(); + stateMachineProcessHandler.initDefaultHandlers(); + loadStateHandlerInterceptors(stateMachineProcessHandler.getStateHandlers()); + + DefaultRouterHandler defaultRouterHandler = new DefaultRouterHandler(); + defaultRouterHandler.setEventPublisher(eventPublisher); + + Map processRouterMap = new HashMap<>(1); + processRouterMap.put(ProcessType.STATE_LANG.getCode(), stateMachineProcessRouter); + defaultRouterHandler.setProcessRouters(processRouterMap); + + CustomizeBusinessProcessor customizeBusinessProcessor = new CustomizeBusinessProcessor(); + + Map processHandlerMap = new HashMap<>(1); + processHandlerMap.put(ProcessType.STATE_LANG.getCode(), stateMachineProcessHandler); + customizeBusinessProcessor.setProcessHandlers(processHandlerMap); + + Map routerHandlerMap = new HashMap<>(1); + routerHandlerMap.put(ProcessType.STATE_LANG.getCode(), defaultRouterHandler); + customizeBusinessProcessor.setRouterHandlers(routerHandlerMap); + + ProcessControllerImpl processorController = new ProcessControllerImpl(); + processorController.setBusinessProcessor(customizeBusinessProcessor); + + return processorController; + } + + protected void loadStateHandlerInterceptors(Map stateHandlerMap) { + for (StateHandler stateHandler : stateHandlerMap.values()) { + if (stateHandler instanceof InterceptableStateHandler) { + InterceptableStateHandler interceptableStateHandler = (InterceptableStateHandler) stateHandler; + List interceptorList = EnhancedServiceLoader.loadAll(StateHandlerInterceptor.class); + for (StateHandlerInterceptor interceptor : interceptorList) { + if (interceptor.match(interceptableStateHandler.getClass())) { + interceptableStateHandler.addInterceptor(interceptor); + } + + if (interceptor instanceof ApplicationContextAware) { + ((ApplicationContextAware) interceptor).setApplicationContext(getApplicationContext()); + } + } + } + } + } + + protected void loadStateRouterInterceptors(Map stateRouterMap) { + for (StateRouter stateRouter : stateRouterMap.values()) { + if (stateRouter instanceof InterceptableStateRouter) { + InterceptableStateRouter interceptableStateRouter = (InterceptableStateRouter) stateRouter; + List interceptorList = EnhancedServiceLoader.loadAll(StateRouterInterceptor.class); + for (StateRouterInterceptor interceptor : interceptorList) { + if (interceptor.match(interceptableStateRouter.getClass())) { + interceptableStateRouter.addInterceptor(interceptor); + } + + if (interceptor instanceof ApplicationContextAware) { + ((ApplicationContextAware) interceptor).setApplicationContext(getApplicationContext()); + } + } + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + init(); + } + + @Override + public StateLogStore getStateLogStore() { + return this.stateLogStore; + } + + public void setStateLogStore(StateLogStore stateLogStore) { + this.stateLogStore = stateLogStore; + } + + @Override + public StateLangStore getStateLangStore() { + return stateLangStore; + } + + public void setStateLangStore(StateLangStore stateLangStore) { + this.stateLangStore = stateLangStore; + } + + @Override + public ExpressionFactoryManager getExpressionFactoryManager() { + return this.expressionFactoryManager; + } + + public void setExpressionFactoryManager(ExpressionFactoryManager expressionFactoryManager) { + this.expressionFactoryManager = expressionFactoryManager; + } + + @Override + public EvaluatorFactoryManager getEvaluatorFactoryManager() { + return this.evaluatorFactoryManager; + } + + public void setEvaluatorFactoryManager(EvaluatorFactoryManager evaluatorFactoryManager) { + this.evaluatorFactoryManager = evaluatorFactoryManager; + } + + @Override + public String getCharset() { + return this.charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + @Override + public StateMachineRepository getStateMachineRepository() { + return stateMachineRepository; + } + + public void setStateMachineRepository(StateMachineRepository stateMachineRepository) { + this.stateMachineRepository = stateMachineRepository; + } + + @Override + public StatusDecisionStrategy getStatusDecisionStrategy() { + return statusDecisionStrategy; + } + + public void setStatusDecisionStrategy(StatusDecisionStrategy statusDecisionStrategy) { + this.statusDecisionStrategy = statusDecisionStrategy; + } + + @Override + public SeqGenerator getSeqGenerator() { + if (seqGenerator == null) { + synchronized (this) { + if (seqGenerator == null) { + seqGenerator = new SpringJvmUUIDSeqGenerator(); + } + } + } + return seqGenerator; + } + + public void setSeqGenerator(SeqGenerator seqGenerator) { + this.seqGenerator = seqGenerator; + } + + @Override + public ProcessCtrlEventPublisher getProcessCtrlEventPublisher() { + return syncProcessCtrlEventPublisher; + } + + @Override + public ProcessCtrlEventPublisher getAsyncProcessCtrlEventPublisher() { + return asyncProcessCtrlEventPublisher; + } + + public void setAsyncProcessCtrlEventPublisher(ProcessCtrlEventPublisher asyncProcessCtrlEventPublisher) { + this.asyncProcessCtrlEventPublisher = asyncProcessCtrlEventPublisher; + } + + @Override + public ApplicationContext getApplicationContext() { + return applicationContext; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public ThreadPoolExecutor getThreadPoolExecutor() { + return threadPoolExecutor; + } + + public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } + + @Override + public boolean isEnableAsync() { + return enableAsync; + } + + public void setEnableAsync(boolean enableAsync) { + this.enableAsync = enableAsync; + } + + @Override + public StateLogRepository getStateLogRepository() { + return stateLogRepository; + } + + public void setStateLogRepository(StateLogRepository stateLogRepository) { + this.stateLogRepository = stateLogRepository; + } + + public void setSyncProcessCtrlEventPublisher(ProcessCtrlEventPublisher syncProcessCtrlEventPublisher) { + this.syncProcessCtrlEventPublisher = syncProcessCtrlEventPublisher; + } + + public void setResources(Resource[] resources) { + this.resources = resources; + } + + @Override + public ServiceInvokerManager getServiceInvokerManager() { + return serviceInvokerManager; + } + + public void setServiceInvokerManager(ServiceInvokerManager serviceInvokerManager) { + this.serviceInvokerManager = serviceInvokerManager; + } + + @Override + public String getDefaultTenantId() { + return defaultTenantId; + } + + public void setDefaultTenantId(String defaultTenantId) { + this.defaultTenantId = defaultTenantId; + } + + @Override + public int getTransOperationTimeout() { + return transOperationTimeout; + } + + public void setTransOperationTimeout(int transOperationTimeout) { + this.transOperationTimeout = transOperationTimeout; + } + + @Override + public int getServiceInvokeTimeout() { + return serviceInvokeTimeout; + } + + public void setServiceInvokeTimeout(int serviceInvokeTimeout) { + this.serviceInvokeTimeout = serviceInvokeTimeout; + } + + @Override + public ScriptEngineManager getScriptEngineManager() { + return scriptEngineManager; + } + + public void setScriptEngineManager(ScriptEngineManager scriptEngineManager) { + this.scriptEngineManager = scriptEngineManager; + } + + public String getSagaJsonParser() { + return sagaJsonParser; + } + + public void setSagaJsonParser(String sagaJsonParser) { + this.sagaJsonParser = sagaJsonParser; + } + + public boolean isSagaRetryPersistModeUpdate() { + return sagaRetryPersistModeUpdate; + } + + public void setSagaRetryPersistModeUpdate(boolean sagaRetryPersistModeUpdate) { + this.sagaRetryPersistModeUpdate = sagaRetryPersistModeUpdate; + } + + public boolean isSagaCompensatePersistModeUpdate() { + return sagaCompensatePersistModeUpdate; + } + + public void setSagaCompensatePersistModeUpdate(boolean sagaCompensatePersistModeUpdate) { + this.sagaCompensatePersistModeUpdate = sagaCompensatePersistModeUpdate; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java new file mode 100644 index 0000000..9ef64fb --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java @@ -0,0 +1,707 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.impl; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.AsyncCallback; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.LoopTaskUtils; +import io.seata.saga.engine.pcext.utils.ParameterUtils; +import io.seata.saga.engine.utils.ProcessContextBuilder; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessType; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.TaskState.Loop; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import io.seata.saga.statelang.domain.impl.CompensationTriggerStateImpl; +import io.seata.saga.statelang.domain.impl.LoopStartStateImpl; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import io.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * ProcessCtrl-based state machine engine + * + * @author lorne.cl + */ +public class ProcessCtrlStateMachineEngine implements StateMachineEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProcessCtrlStateMachineEngine.class); + + private StateMachineConfig stateMachineConfig; + + private static void nullSafeCopy(Map srcMap, Map destMap) { + srcMap.forEach((key, value) -> { + if (value != null) { + destMap.put(key, value); + } + }); + } + + @Override + public StateMachineInstance start(String stateMachineName, String tenantId, Map startParams) + throws EngineExecutionException { + + return startInternal(stateMachineName, tenantId, null, startParams, false, null); + } + + @Override + public StateMachineInstance startAsync(String stateMachineName, String tenantId, Map startParams, + AsyncCallback callback) throws EngineExecutionException { + + return startInternal(stateMachineName, tenantId, null, startParams, true, callback); + } + + @Override + public StateMachineInstance startWithBusinessKey(String stateMachineName, String tenantId, String businessKey, + Map startParams) throws EngineExecutionException { + + return startInternal(stateMachineName, tenantId, businessKey, startParams, false, null); + } + + @Override + public StateMachineInstance startWithBusinessKeyAsync(String stateMachineName, String tenantId, String businessKey, + Map startParams, AsyncCallback callback) + throws EngineExecutionException { + + return startInternal(stateMachineName, tenantId, businessKey, startParams, true, callback); + } + + private StateMachineInstance startInternal(String stateMachineName, String tenantId, String businessKey, + Map startParams, boolean async, AsyncCallback callback) + throws EngineExecutionException { + + if (async && !stateMachineConfig.isEnableAsync()) { + throw new EngineExecutionException( + "Asynchronous start is disabled. please set StateMachineConfig.enableAsync=true first.", + FrameworkErrorCode.AsynchronousStartDisabled); + } + + if (StringUtils.isEmpty(tenantId)) { + tenantId = stateMachineConfig.getDefaultTenantId(); + } + + StateMachineInstance instance = createMachineInstance(stateMachineName, tenantId, businessKey, startParams); + + ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG) + .withOperationName(DomainConstants.OPERATION_NAME_START).withAsyncCallback(callback).withInstruction( + new StateInstruction(stateMachineName, tenantId)).withStateMachineInstance(instance) + .withStateMachineConfig(getStateMachineConfig()).withStateMachineEngine(this); + + Map contextVariables; + if (startParams != null) { + contextVariables = new ConcurrentHashMap<>(startParams.size()); + nullSafeCopy(startParams, contextVariables); + } else { + contextVariables = new ConcurrentHashMap<>(); + } + instance.setContext(contextVariables); + + contextBuilder.withStateMachineContextVariables(contextVariables); + + contextBuilder.withIsAsyncExecution(async); + + ProcessContext processContext = contextBuilder.build(); + + if (instance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) { + stateMachineConfig.getStateLogStore().recordStateMachineStarted(instance, processContext); + } + if (StringUtils.isEmpty(instance.getId())) { + instance.setId( + stateMachineConfig.getSeqGenerator().generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE_INST)); + } + + StateInstruction stateInstruction = processContext.getInstruction(StateInstruction.class); + Loop loop = LoopTaskUtils.getLoopConfig(processContext, stateInstruction.getState(processContext)); + if (null != loop) { + stateInstruction.setTemporaryState(new LoopStartStateImpl()); + } + + if (async) { + stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(processContext); + } else { + stateMachineConfig.getProcessCtrlEventPublisher().publish(processContext); + } + + return instance; + } + + private StateMachineInstance createMachineInstance(String stateMachineName, String tenantId, String businessKey, + Map startParams) { + StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine(stateMachineName, + tenantId); + if (stateMachine == null) { + throw new EngineExecutionException("StateMachine[" + stateMachineName + "] is not exists", + FrameworkErrorCode.ObjectNotExists); + } + + StateMachineInstanceImpl inst = new StateMachineInstanceImpl(); + inst.setStateMachine(stateMachine); + inst.setMachineId(stateMachine.getId()); + inst.setTenantId(tenantId); + inst.setBusinessKey(businessKey); + + inst.setStartParams(startParams); + if (startParams != null) { + if (StringUtils.hasText(businessKey)) { + startParams.put(DomainConstants.VAR_NAME_BUSINESSKEY, businessKey); + } + + String parentId = (String)startParams.get(DomainConstants.VAR_NAME_PARENT_ID); + if (StringUtils.hasText(parentId)) { + inst.setParentId(parentId); + startParams.remove(DomainConstants.VAR_NAME_PARENT_ID); + } + } + + inst.setStatus(ExecutionStatus.RU); + + inst.setRunning(true); + + inst.setGmtStarted(new Date()); + inst.setGmtUpdated(inst.getGmtStarted()); + + return inst; + } + + @Override + public StateMachineInstance forward(String stateMachineInstId, Map replaceParams) + throws EngineExecutionException { + return forwardInternal(stateMachineInstId, replaceParams, false, false, null); + } + + @Override + public StateMachineInstance forwardAsync(String stateMachineInstId, Map replaceParams, + AsyncCallback callback) throws EngineExecutionException { + return forwardInternal(stateMachineInstId, replaceParams, false, true, callback); + } + + protected StateMachineInstance forwardInternal(String stateMachineInstId, Map replaceParams, + boolean skip, boolean async, AsyncCallback callback) + throws EngineExecutionException { + + StateMachineInstance stateMachineInstance = reloadStateMachineInstance(stateMachineInstId); + + if (stateMachineInstance == null) { + throw new ForwardInvalidException("StateMachineInstance is not exits", + FrameworkErrorCode.StateMachineInstanceNotExists); + } + if (ExecutionStatus.SU.equals(stateMachineInstance.getStatus()) + && stateMachineInstance.getCompensationStatus() == null) { + return stateMachineInstance; + } + + ExecutionStatus[] acceptStatus = new ExecutionStatus[] {ExecutionStatus.FA, ExecutionStatus.UN, ExecutionStatus.RU}; + checkStatus(stateMachineInstance, acceptStatus, null, stateMachineInstance.getStatus(), null, "forward"); + + List actList = stateMachineInstance.getStateList(); + if (CollectionUtils.isEmpty(actList)) { + throw new ForwardInvalidException("StateMachineInstance[id:" + stateMachineInstId + + "] has no stateInstance, pls start a new StateMachine execution instead", + FrameworkErrorCode.OperationDenied); + } + + StateInstance lastForwardState = findOutLastForwardStateInstance(actList); + + if (lastForwardState == null) { + throw new ForwardInvalidException( + "StateMachineInstance[id:" + stateMachineInstId + "] Cannot find last forward execution stateInstance", + FrameworkErrorCode.OperationDenied); + } + + ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG) + .withOperationName(DomainConstants.OPERATION_NAME_FORWARD).withAsyncCallback(callback) + .withStateMachineInstance(stateMachineInstance).withStateInstance(lastForwardState).withStateMachineConfig( + getStateMachineConfig()).withStateMachineEngine(this); + + contextBuilder.withIsAsyncExecution(async); + + ProcessContext context = contextBuilder.build(); + + Map contextVariables = getStateMachineContextVariables(stateMachineInstance); + + if (replaceParams != null) { + contextVariables.putAll(replaceParams); + } + putBusinesskeyToContextariables(stateMachineInstance, contextVariables); + + ConcurrentHashMap concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size()); + nullSafeCopy(contextVariables, concurrentContextVariables); + + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, concurrentContextVariables); + stateMachineInstance.setContext(concurrentContextVariables); + + String originStateName = EngineUtils.getOriginStateName(lastForwardState); + State lastState = stateMachineInstance.getStateMachine().getState(originStateName); + Loop loop = LoopTaskUtils.getLoopConfig(context, lastState); + if (null != loop && ExecutionStatus.SU.equals(lastForwardState.getStatus())) { + lastForwardState = LoopTaskUtils.findOutLastNeedForwardStateInstance(context); + } + + context.setVariable(lastForwardState.getName() + DomainConstants.VAR_NAME_RETRIED_STATE_INST_ID, + lastForwardState.getId()); + if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(lastForwardState.getType()) && !ExecutionStatus.SU + .equals(lastForwardState.getCompensationStatus())) { + + context.setVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD, true); + } + + if (!ExecutionStatus.SU.equals(lastForwardState.getStatus())) { + lastForwardState.setIgnoreStatus(true); + } + + try { + StateInstruction inst = new StateInstruction(); + inst.setTenantId(stateMachineInstance.getTenantId()); + inst.setStateMachineName(stateMachineInstance.getStateMachine().getName()); + if (skip || ExecutionStatus.SU.equals(lastForwardState.getStatus())) { + + String next = null; + State state = stateMachineInstance.getStateMachine().getState(EngineUtils.getOriginStateName(lastForwardState)); + if (state != null && state instanceof AbstractTaskState) { + next = ((AbstractTaskState)state).getNext(); + } + if (StringUtils.isEmpty(next)) { + LOGGER.warn( + "Last Forward execution StateInstance was succeed, and it has not Next State , skip forward " + + "operation"); + return stateMachineInstance; + } + inst.setStateName(next); + } else { + + if (ExecutionStatus.RU.equals(lastForwardState.getStatus()) + && !EngineUtils.isTimeout(lastForwardState.getGmtStarted(), stateMachineConfig.getServiceInvokeTimeout())) { + throw new EngineExecutionException( + "State [" + lastForwardState.getName() + "] is running, operation[forward] denied", FrameworkErrorCode.OperationDenied); + } + + inst.setStateName(EngineUtils.getOriginStateName(lastForwardState)); + } + context.setInstruction(inst); + + stateMachineInstance.setStatus(ExecutionStatus.RU); + stateMachineInstance.setRunning(true); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Operation [forward] started stateMachineInstance[id:" + stateMachineInstance.getId() + "]"); + } + + if (stateMachineInstance.getStateMachine().isPersist()) { + stateMachineConfig.getStateLogStore().recordStateMachineRestarted(stateMachineInstance, context); + } + + loop = LoopTaskUtils.getLoopConfig(context, inst.getState(context)); + if (null != loop) { + inst.setTemporaryState(new LoopStartStateImpl()); + } + + if (async) { + stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(context); + } else { + stateMachineConfig.getProcessCtrlEventPublisher().publish(context); + } + } catch (EngineExecutionException e) { + LOGGER.error("Operation [forward] failed", e); + throw e; + } + return stateMachineInstance; + } + + private Map getStateMachineContextVariables(StateMachineInstance stateMachineInstance) { + Map contextVariables = stateMachineInstance.getEndParams(); + if (CollectionUtils.isEmpty(contextVariables)) { + contextVariables = replayContextVariables(stateMachineInstance); + } + return contextVariables; + } + + protected Map replayContextVariables(StateMachineInstance stateMachineInstance) { + Map contextVariables = new HashMap<>(); + if (stateMachineInstance.getStartParams() == null) { + contextVariables.putAll(stateMachineInstance.getStartParams()); + } + + List stateInstanceList = stateMachineInstance.getStateList(); + if (CollectionUtils.isEmpty(stateInstanceList)) { + return contextVariables; + } + + for (StateInstance stateInstance : stateInstanceList) { + Object serviceOutputParams = stateInstance.getOutputParams(); + if (serviceOutputParams != null) { + ServiceTaskStateImpl state = (ServiceTaskStateImpl)stateMachineInstance.getStateMachine().getState( + EngineUtils.getOriginStateName(stateInstance)); + if (state == null) { + throw new EngineExecutionException( + "Cannot find State by state name [" + stateInstance.getName() + "], may be this is a bug", + FrameworkErrorCode.ObjectNotExists); + } + + if (CollectionUtils.isNotEmpty(state.getOutput())) { + try { + Map outputVariablesToContext = ParameterUtils + .createOutputParams(stateMachineConfig.getExpressionFactoryManager(), state, + serviceOutputParams); + if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { + contextVariables.putAll(outputVariablesToContext); + } + + if (StringUtils.hasLength(stateInstance.getBusinessKey())) { + contextVariables.put( + state.getName() + DomainConstants.VAR_NAME_BUSINESSKEY, + stateInstance.getBusinessKey()); + } + } catch (Exception e) { + throw new EngineExecutionException(e, "Context variables replay faied", + FrameworkErrorCode.ContextVariableReplayFailed); + } + } + } + } + return contextVariables; + } + + /** + * Find the last instance of the forward execution state + * + * @param stateInstanceList + * @return + */ + public StateInstance findOutLastForwardStateInstance(List stateInstanceList) { + StateInstance lastForwardStateInstance = null; + for (int i = stateInstanceList.size() - 1; i >= 0; i--) { + StateInstance stateInstance = stateInstanceList.get(i); + if (!stateInstance.isForCompensation()) { + + if (ExecutionStatus.SU.equals(stateInstance.getCompensationStatus())) { + continue; + } + + if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(stateInstance.getType())) { + + StateInstance finalState = stateInstance; + + while (StringUtils.hasText(finalState.getStateIdRetriedFor())) { + finalState = stateMachineConfig.getStateLogStore().getStateInstance( + finalState.getStateIdRetriedFor(), finalState.getMachineInstanceId()); + } + + List subInst = stateMachineConfig.getStateLogStore() + .queryStateMachineInstanceByParentId(EngineUtils.generateParentId(finalState)); + if (CollectionUtils.isNotEmpty(subInst)) { + if (ExecutionStatus.SU.equals(subInst.get(0).getCompensationStatus())) { + continue; + } + + if (ExecutionStatus.UN.equals(subInst.get(0).getCompensationStatus())) { + throw new ForwardInvalidException( + "Last forward execution state instance is SubStateMachine and compensation status is " + + "[UN], Operation[forward] denied, stateInstanceId:" + + stateInstance.getId(), FrameworkErrorCode.OperationDenied); + } + + } + } else if (ExecutionStatus.UN.equals(stateInstance.getCompensationStatus())) { + + throw new ForwardInvalidException( + "Last forward execution state instance compensation status is [UN], Operation[forward] " + + "denied, stateInstanceId:" + + stateInstance.getId(), FrameworkErrorCode.OperationDenied); + } + + lastForwardStateInstance = stateInstance; + break; + } + } + return lastForwardStateInstance; + } + + @Override + public StateMachineInstance compensate(String stateMachineInstId, Map replaceParams) + throws EngineExecutionException { + return compensateInternal(stateMachineInstId, replaceParams, false, null); + } + + @Override + public StateMachineInstance compensateAsync(String stateMachineInstId, Map replaceParams, + AsyncCallback callback) throws EngineExecutionException { + return compensateInternal(stateMachineInstId, replaceParams, true, callback); + } + + public StateMachineInstance compensateInternal(String stateMachineInstId, Map replaceParams, + boolean async, AsyncCallback callback) + throws EngineExecutionException { + + StateMachineInstance stateMachineInstance = reloadStateMachineInstance(stateMachineInstId); + + if (stateMachineInstance == null) { + throw new EngineExecutionException("StateMachineInstance is not exits", + FrameworkErrorCode.StateMachineInstanceNotExists); + } + + if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) { + return stateMachineInstance; + } + + if (stateMachineInstance.getCompensationStatus() != null) { + ExecutionStatus[] denyStatus = new ExecutionStatus[] {ExecutionStatus.SU}; + checkStatus(stateMachineInstance, null, denyStatus, null, stateMachineInstance.getCompensationStatus(), + "compensate"); + } + + if (replaceParams != null) { + stateMachineInstance.getEndParams().putAll(replaceParams); + } + + ProcessContextBuilder contextBuilder = ProcessContextBuilder.create().withProcessType(ProcessType.STATE_LANG) + .withOperationName(DomainConstants.OPERATION_NAME_COMPENSATE).withAsyncCallback(callback) + .withStateMachineInstance(stateMachineInstance).withStateMachineConfig(getStateMachineConfig()) + .withStateMachineEngine(this); + + contextBuilder.withIsAsyncExecution(async); + + ProcessContext context = contextBuilder.build(); + + Map contextVariables = getStateMachineContextVariables(stateMachineInstance); + + if (replaceParams != null) { + contextVariables.putAll(replaceParams); + } + putBusinesskeyToContextariables(stateMachineInstance, contextVariables); + + ConcurrentHashMap concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size()); + nullSafeCopy(contextVariables, concurrentContextVariables); + + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, concurrentContextVariables); + stateMachineInstance.setContext(concurrentContextVariables); + + CompensationTriggerStateImpl tempCompensationTriggerState = new CompensationTriggerStateImpl(); + tempCompensationTriggerState.setStateMachine(stateMachineInstance.getStateMachine()); + + stateMachineInstance.setRunning(true); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Operation [compensate] start. stateMachineInstance[id:" + stateMachineInstance.getId() + "]"); + } + + if (stateMachineInstance.getStateMachine().isPersist()) { + stateMachineConfig.getStateLogStore().recordStateMachineRestarted(stateMachineInstance, context); + } + try { + StateInstruction inst = new StateInstruction(); + inst.setTenantId(stateMachineInstance.getTenantId()); + inst.setStateMachineName(stateMachineInstance.getStateMachine().getName()); + inst.setTemporaryState(tempCompensationTriggerState); + + context.setInstruction(inst); + + if (async) { + stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(context); + } else { + stateMachineConfig.getProcessCtrlEventPublisher().publish(context); + } + + } catch (EngineExecutionException e) { + LOGGER.error("Operation [compensate] failed", e); + throw e; + } + + return stateMachineInstance; + } + + @Override + public StateMachineInstance skipAndForward(String stateMachineInstId, Map replaceParams) throws EngineExecutionException { + return forwardInternal(stateMachineInstId, replaceParams, false, true, null); + } + + @Override + public StateMachineInstance skipAndForwardAsync(String stateMachineInstId, AsyncCallback callback) + throws EngineExecutionException { + return forwardInternal(stateMachineInstId, null, false, true, callback); + } + + /** + * override state machine instance + * + * @param instId + * @return + */ + @Override + public StateMachineInstance reloadStateMachineInstance(String instId) { + + StateMachineInstance inst = stateMachineConfig.getStateLogStore().getStateMachineInstance(instId); + if (inst != null) { + StateMachine stateMachine = inst.getStateMachine(); + if (stateMachine == null) { + stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachineById(inst.getMachineId()); + inst.setStateMachine(stateMachine); + } + if (stateMachine == null) { + throw new EngineExecutionException("StateMachine[id:" + inst.getMachineId() + "] not exist.", + FrameworkErrorCode.ObjectNotExists); + } + + List stateList = inst.getStateList(); + if (CollectionUtils.isEmpty(stateList)) { + stateList = stateMachineConfig.getStateLogStore().queryStateInstanceListByMachineInstanceId(instId); + if (CollectionUtils.isNotEmpty(stateList)) { + for (StateInstance tmpStateInstance : stateList) { + inst.putStateInstance(tmpStateInstance.getId(), tmpStateInstance); + } + } + } + + if (CollectionUtils.isEmpty(inst.getEndParams())) { + inst.setEndParams(replayContextVariables(inst)); + } + } + return inst; + } + + /** + * Check if the status is legal + * + * @param stateMachineInstance + * @param acceptStatus + * @param denyStatus + * @param status + * @param compenStatus + * @param operation + * @return + */ + protected boolean checkStatus(StateMachineInstance stateMachineInstance, ExecutionStatus[] acceptStatus, + ExecutionStatus[] denyStatus, ExecutionStatus status, ExecutionStatus compenStatus, + String operation) { + if (status != null && compenStatus != null) { + throw new EngineExecutionException("status and compensationStatus are not supported at the same time", + FrameworkErrorCode.InvalidParameter); + } + if (status == null && compenStatus == null) { + throw new EngineExecutionException("status and compensationStatus must input at least one", + FrameworkErrorCode.InvalidParameter); + } + if (ExecutionStatus.SU.equals(compenStatus)) { + String message = buildExceptionMessage(stateMachineInstance, null, null, null, ExecutionStatus.SU, + operation); + throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied); + } + + if (stateMachineInstance.isRunning() && !EngineUtils.isTimeout(stateMachineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) { + throw new EngineExecutionException( + "StateMachineInstance [id:" + stateMachineInstance.getId() + "] is running, operation[" + operation + + "] denied", FrameworkErrorCode.OperationDenied); + } + + if ((denyStatus == null || denyStatus.length == 0) && (acceptStatus == null || acceptStatus.length == 0)) { + throw new EngineExecutionException("StateMachineInstance[id:" + stateMachineInstance.getId() + + "], acceptable status and deny status must input at least one", FrameworkErrorCode.InvalidParameter); + } + + ExecutionStatus currentStatus = (status != null) ? status : compenStatus; + + if (!(denyStatus == null || denyStatus.length == 0)) { + for (ExecutionStatus tempDenyStatus : denyStatus) { + if (tempDenyStatus.compareTo(currentStatus) == 0) { + String message = buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, + compenStatus, operation); + throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied); + } + } + } + + if (acceptStatus == null || acceptStatus.length == 0) { + return true; + } else { + for (ExecutionStatus tempStatus : acceptStatus) { + if (tempStatus.compareTo(currentStatus) == 0) { + return true; + } + } + } + + String message = buildExceptionMessage(stateMachineInstance, acceptStatus, denyStatus, status, compenStatus, + operation); + throw new EngineExecutionException(message, FrameworkErrorCode.OperationDenied); + } + + private String buildExceptionMessage(StateMachineInstance stateMachineInstance, ExecutionStatus[] acceptStatus, + ExecutionStatus[] denyStatus, ExecutionStatus status, + ExecutionStatus compenStatus, String operation) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("StateMachineInstance[id:").append(stateMachineInstance.getId()).append("]"); + if (acceptStatus != null) { + stringBuilder.append(",acceptable status :"); + for (ExecutionStatus tempStatus : acceptStatus) { + stringBuilder.append(tempStatus.toString()); + stringBuilder.append(" "); + } + } + if (denyStatus != null) { + stringBuilder.append(",deny status:"); + for (ExecutionStatus tempStatus : denyStatus) { + stringBuilder.append(tempStatus.toString()); + stringBuilder.append(" "); + } + } + if (status != null) { + stringBuilder.append(",current status:"); + stringBuilder.append(status.toString()); + } + if (compenStatus != null) { + stringBuilder.append(",current compensation status:"); + stringBuilder.append(compenStatus.toString()); + } + stringBuilder.append(",so operation [").append(operation).append("] denied"); + return stringBuilder.toString(); + } + + private void putBusinesskeyToContextariables(StateMachineInstance stateMachineInstance, + Map contextVariables) { + if (StringUtils.hasText(stateMachineInstance.getBusinessKey()) && !contextVariables.containsKey( + DomainConstants.VAR_NAME_BUSINESSKEY)) { + contextVariables.put(DomainConstants.VAR_NAME_BUSINESSKEY, stateMachineInstance.getBusinessKey()); + } + } + + @Override + public StateMachineConfig getStateMachineConfig() { + return stateMachineConfig; + } + + public void setStateMachineConfig(StateMachineConfig stateMachineConfig) { + this.stateMachineConfig = stateMachineConfig; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvoker.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvoker.java new file mode 100644 index 0000000..9e0735b --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvoker.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.invoker; + +import io.seata.saga.statelang.domain.ServiceTaskState; + +/** + * Service invoker + * + * @author lorne.cl + */ +public interface ServiceInvoker { + + /** + * invoke service + * @param serviceTaskState + * @param input + * @return + * @throws Throwable + */ + Object invoke(ServiceTaskState serviceTaskState, Object... input) throws Throwable; +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvokerManager.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvokerManager.java new file mode 100644 index 0000000..d165116 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/ServiceInvokerManager.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.invoker; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.saga.statelang.domain.DomainConstants; +import org.springframework.util.StringUtils; + +/** + * Service Invoker Manager + * + * @author lorne.cl + */ +public class ServiceInvokerManager { + + private Map serviceInvokerMap = new ConcurrentHashMap<>(); + + public ServiceInvoker getServiceInvoker(String serviceType) { + if (StringUtils.isEmpty(serviceType)) { + serviceType = DomainConstants.SERVICE_TYPE_SPRING_BEAN; + } + return serviceInvokerMap.get(serviceType); + } + + public void putServiceInvoker(String serviceType, ServiceInvoker serviceInvoker) { + serviceInvokerMap.put(serviceType, serviceInvoker); + } + + public Map getServiceInvokerMap() { + return serviceInvokerMap; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java new file mode 100644 index 0000000..9f4908a --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/invoker/impl/SpringBeanServiceInvoker.java @@ -0,0 +1,359 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.invoker.impl; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.invoker.ServiceInvoker; +import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.statelang.domain.ServiceTaskState; +import io.seata.saga.statelang.domain.TaskState.Retry; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import io.seata.saga.statelang.parser.JsonParser; +import io.seata.saga.statelang.parser.JsonParserFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * SpringBean Service Invoker + * + * @author lorne.cl + */ +public class SpringBeanServiceInvoker implements ServiceInvoker, ApplicationContextAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringBeanServiceInvoker.class); + + private ApplicationContext applicationContext; + private ThreadPoolExecutor threadPoolExecutor; + private String sagaJsonParser; + + @Override + public Object invoke(ServiceTaskState serviceTaskState, Object... input) throws Throwable { + ServiceTaskStateImpl state = (ServiceTaskStateImpl) serviceTaskState; + if (state.isAsync()) { + if (threadPoolExecutor == null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn( + "threadPoolExecutor is null, Service[{}.{}] cannot execute asynchronously, executing " + + "synchronously now. stateName: {}", + state.getServiceName(), state.getServiceMethod(), state.getName()); + } + return doInvoke(state, input); + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Submit Service[{}.{}] to asynchronously executing. stateName: {}", state.getServiceName(), + state.getServiceMethod(), state.getName()); + } + threadPoolExecutor.execute(new Runnable() { + @Override + public void run() { + try { + doInvoke(state, input); + } catch (Throwable e) { + LOGGER.error("Invoke Service[" + state.getServiceName() + "." + state.getServiceMethod() + "] failed.", e); + } + } + }); + return null; + } else { + return doInvoke(state, input); + } + } + + protected Object doInvoke(ServiceTaskStateImpl state, Object[] input) throws Throwable { + + Object bean = applicationContext.getBean(state.getServiceName()); + + Method method = state.getMethod(); + if (method == null) { + synchronized (state) { + method = state.getMethod(); + if (method == null) { + method = findMethod(bean.getClass(), state.getServiceMethod(), state.getParameterTypes()); + if (method != null) { + state.setMethod(method); + } + } + } + } + + if (method == null) { + throw new EngineExecutionException( + "No such method[" + state.getServiceMethod() + "] on BeanClass[" + bean.getClass() + "]", + FrameworkErrorCode.NoSuchMethod); + + } + + Object[] args = new Object[method.getParameterCount()]; + try { + Class[] paramTypes = method.getParameterTypes(); + if (input != null && input.length > 0) { + int len = input.length < paramTypes.length ? input.length : paramTypes.length; + for (int i = 0; i < len; i++) { + args[i] = toJavaObject(input[i], paramTypes[i]); + } + } + } catch (Exception e) { + throw new EngineExecutionException(e, + "Input to java object error, Method[" + state.getServiceMethod() + "] on BeanClass[" + bean.getClass() + + "]", FrameworkErrorCode.InvalidParameter); + } + + if (!Modifier.isPublic(method.getModifiers())) { + throw new EngineExecutionException("Method[" + method.getName() + "] must be public", + FrameworkErrorCode.MethodNotPublic); + } + + Map retryCountMap = new HashMap<>(); + while (true) { + try { + return invokeMethod(bean, method, args); + } catch (Throwable e) { + Retry matchedRetryConfig = matchRetryConfig(state.getRetry(), e); + if (matchedRetryConfig == null) { + throw e; + } + + AtomicInteger retryCount = CollectionUtils.computeIfAbsent(retryCountMap, matchedRetryConfig, + key -> new AtomicInteger(0)); + if (retryCount.intValue() >= matchedRetryConfig.getMaxAttempts()) { + throw e; + } + + double intervalSeconds = matchedRetryConfig.getIntervalSeconds(); + double backoffRate = matchedRetryConfig.getBackoffRate(); + long currentInterval = (long) (retryCount.intValue() > 0 ? + (intervalSeconds * backoffRate * retryCount.intValue() * 1000) : (intervalSeconds * 1000)); + + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Invoke Service[" + state.getServiceName() + "." + state.getServiceMethod() + "] failed, will retry after " + + currentInterval + " millis, current retry count: " + retryCount.intValue(), e); + } + try { + Thread.sleep(currentInterval); + } catch (InterruptedException e1) { + LOGGER.warn("Retry interval sleep error", e1); + } + retryCount.incrementAndGet(); + } + } + } + + private Retry matchRetryConfig(List retryList, Throwable e) { + if (CollectionUtils.isNotEmpty(retryList)) { + for (Retry retryConfig : retryList) { + List exceptions = retryConfig.getExceptions(); + if (CollectionUtils.isEmpty(exceptions)) { + // Exceptions not configured, Match current exception if it is NetException. + if (ExceptionUtils.isNetException(e)) { + return retryConfig; + } + } else { + List> exceptionClasses = retryConfig.getExceptionClasses(); + if (exceptionClasses == null) { + synchronized (retryConfig) { + exceptionClasses = retryConfig.getExceptionClasses(); + if (exceptionClasses == null) { + + exceptionClasses = new ArrayList<>(exceptions.size()); + for (String expStr : exceptions) { + + Class expClass = null; + try { + expClass = (Class) ServiceTaskStateHandler.class + .getClassLoader().loadClass(expStr); + } catch (Exception e1) { + + LOGGER.warn("Cannot Load Exception Class by getClass().getClassLoader()", e1); + + try { + expClass = (Class) Thread.currentThread() + .getContextClassLoader().loadClass(expStr); + } catch (Exception e2) { + LOGGER.warn( + "Cannot Load Exception Class by Thread.currentThread()" + + ".getContextClassLoader()", + e2); + } + } + + if (expClass != null) { + exceptionClasses.add(expClass); + } + } + retryConfig.setExceptionClasses(exceptionClasses); + } + } + } + + for (Class expClass : exceptionClasses) { + if (expClass.isAssignableFrom(e.getClass())) { + return retryConfig; + } + } + + } + } + } + return null; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } + + protected Method findMethod(Class clazz, String methodName, List parameterTypes) { + if (CollectionUtils.isEmpty(parameterTypes)) { + return BeanUtils.findDeclaredMethodWithMinimalParameters(clazz, methodName); + } else { + Class[] paramClassTypes = new Class[parameterTypes.size()]; + for (int i = 0; i < parameterTypes.size(); i++) { + paramClassTypes[i] = classForName(parameterTypes.get(i)); + } + return BeanUtils.findDeclaredMethod(clazz, methodName, paramClassTypes); + } + } + + protected Class classForName(String className) { + Class clazz = getPrimitiveClass(className); + if (clazz == null) { + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + LOGGER.error(e.getMessage(), e); + } + } + if (clazz == null) { + try { + clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + LOGGER.error(e.getMessage(), e); + } + } + if (clazz == null) { + throw new EngineExecutionException("Parameter class not found [" + className + "]", + FrameworkErrorCode.ObjectNotExists); + } + return clazz; + } + + protected Object invokeMethod(Object serviceBean, Method method, Object... input) throws Throwable { + try { + return method.invoke(serviceBean, input); + } catch (InvocationTargetException e) { + Throwable targetExp = e.getTargetException(); + if (targetExp == null) { + throw new EngineExecutionException(e, e.getMessage(), FrameworkErrorCode.MethodInvokeError); + } + + throw targetExp; + } + } + + protected Object toJavaObject(Object value, Class paramType) { + if (value == null) { + return value; + } + + if (paramType.isAssignableFrom(value.getClass())) { + return value; + } else if (isPrimitive(paramType)) { + return value; + } else { + JsonParser jsonParser = JsonParserFactory.getJsonParser(getSagaJsonParser()); + if (jsonParser == null) { + throw new RuntimeException("Cannot get JsonParser by name : " + getSagaJsonParser()); + } + String jsonValue = jsonParser.toJsonString(value, true, false); + return jsonParser.parse(jsonValue, paramType, false); + } + } + + protected boolean isPrimitive(Class clazz) { + return clazz.isPrimitive() // + || clazz == Boolean.class // + || clazz == Character.class // + || clazz == Byte.class // + || clazz == Short.class // + || clazz == Integer.class // + || clazz == Long.class // + || clazz == Float.class // + || clazz == Double.class // + || clazz == BigInteger.class // + || clazz == BigDecimal.class // + || clazz == String.class // + || clazz == java.util.Date.class // + || clazz == java.sql.Date.class // + || clazz == java.sql.Time.class // + || clazz == java.sql.Timestamp.class // + || clazz.isEnum() // + ; + } + + protected Class getPrimitiveClass(String className) { + + if (boolean.class.getName().equals(className)) { + return boolean.class; + } else if (char.class.getName().equals(className)) { + return char.class; + } else if (byte.class.getName().equals(className)) { + return byte.class; + } else if (short.class.getName().equals(className)) { + return short.class; + } else if (int.class.getName().equals(className)) { + return int.class; + } else if (long.class.getName().equals(className)) { + return long.class; + } else if (float.class.getName().equals(className)) { + return float.class; + } else if (double.class.getName().equals(className)) { + return double.class; + } else { + return null; + } + } + + public String getSagaJsonParser() { + return sagaJsonParser; + } + + public void setSagaJsonParser(String sagaJsonParser) { + this.sagaJsonParser = sagaJsonParser; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateHandler.java new file mode 100644 index 0000000..33c70f2 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import java.util.List; + +/** + * Interceptible State Handler + * + * @author lorne.cl + */ +public interface InterceptableStateHandler extends StateHandler { + + List getInterceptors(); + + void addInterceptor(StateHandlerInterceptor interceptor); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateRouter.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateRouter.java new file mode 100644 index 0000000..96d8d4f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/InterceptableStateRouter.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import java.util.List; + +/** + * Interceptable State Router + * + * @author lorne.cl + */ +public interface InterceptableStateRouter extends StateRouter { + + List getInterceptors(); + + void addInterceptor(StateRouterInterceptor interceptor); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandler.java new file mode 100644 index 0000000..9cea74e --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.proctrl.ProcessContext; + +/** + * State Handler + * + * @author lorne.cl + */ +public interface StateHandler { + + void process(ProcessContext context) throws EngineExecutionException; +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandlerInterceptor.java new file mode 100644 index 0000000..8243b4c --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateHandlerInterceptor.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.proctrl.ProcessContext; + +/** + * StateHandler Interceptor + * + * @author lorne.cl + */ +public interface StateHandlerInterceptor { + + void preProcess(ProcessContext context) throws EngineExecutionException; + + void postProcess(ProcessContext context, Exception e) throws EngineExecutionException; + + boolean match(Class clazz); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateInstruction.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateInstruction.java new file mode 100644 index 0000000..8a82392 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateInstruction.java @@ -0,0 +1,172 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; +import org.springframework.util.StringUtils; + +/** + * State Instruction + * + * @author lorne.cl + * @see Instruction + */ +public class StateInstruction implements Instruction { + + private String stateName; + private String stateMachineName; + private String tenantId; + private boolean end; + + /** + * Temporary state node for running temporary nodes in the state machine + */ + private State temporaryState; + + public StateInstruction() { + } + + public StateInstruction(String stateMachineName, String tenantId) { + this.stateMachineName = stateMachineName; + this.tenantId = tenantId; + } + + public State getState(ProcessContext context) { + + if (getTemporaryState() != null) { + + return temporaryState; + } + + String stateName = getStateName(); + String stateMachineName = getStateMachineName(); + String tenantId = getTenantId(); + + if (StringUtils.isEmpty(stateMachineName)) { + throw new EngineExecutionException("StateMachineName is required", FrameworkErrorCode.ParameterRequired); + } + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine(stateMachineName, + tenantId); + if (stateMachine == null) { + throw new EngineExecutionException("StateMachine[" + stateMachineName + "] is not exist", + FrameworkErrorCode.ObjectNotExists); + } + + if (StringUtils.isEmpty(stateName)) { + + stateName = stateMachine.getStartState(); + setStateName(stateName); + } + + State state = stateMachine.getStates().get(stateName); + if (state == null) { + throw new EngineExecutionException("State[" + stateName + "] is not exist", + FrameworkErrorCode.ObjectNotExists); + } + + return state; + } + + /** + * Gets get state name. + * + * @return the get state name + */ + public String getStateName() { + return stateName; + } + + /** + * Sets set state name. + * + * @param stateName the state name + */ + public void setStateName(String stateName) { + this.stateName = stateName; + } + + /** + * Gets get state machine name. + * + * @return the get state machine name + */ + public String getStateMachineName() { + return stateMachineName; + } + + /** + * Sets set state machine name. + * + * @param stateMachineName the state machine name + */ + public void setStateMachineName(String stateMachineName) { + this.stateMachineName = stateMachineName; + } + + /** + * Is end boolean. + * + * @return the boolean + */ + public boolean isEnd() { + return end; + } + + /** + * Sets set end. + * + * @param end the end + */ + public void setEnd(boolean end) { + this.end = end; + } + + /** + * Gets get temporary state. + * + * @return the get temporary state + */ + public State getTemporaryState() { + return temporaryState; + } + + /** + * Sets set temporary state. + * + * @param temporaryState the temporary state + */ + public void setTemporaryState(State temporaryState) { + this.temporaryState = temporaryState; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessHandler.java new file mode 100644 index 0000000..9bee552 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessHandler.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.pcext.handlers.ChoiceStateHandler; +import io.seata.saga.engine.pcext.handlers.CompensationTriggerStateHandler; +import io.seata.saga.engine.pcext.handlers.FailEndStateHandler; +import io.seata.saga.engine.pcext.handlers.LoopStartStateHandler; +import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler; +import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler; +import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler; +import io.seata.saga.engine.pcext.handlers.SucceedEndStateHandler; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.handler.ProcessHandler; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.State; + +/** + * StateMachine ProcessHandler + * + * @author lorne.cl + * @see ProcessHandler + */ +public class StateMachineProcessHandler implements ProcessHandler { + + private final Map stateHandlers = new ConcurrentHashMap<>(); + + @Override + public void process(ProcessContext context) throws FrameworkException { + StateInstruction instruction = context.getInstruction(StateInstruction.class); + State state = instruction.getState(context); + String stateType = state.getType(); + StateHandler stateHandler = stateHandlers.get(stateType); + + List interceptors = null; + if (stateHandler instanceof InterceptableStateHandler) { + interceptors = ((InterceptableStateHandler)stateHandler).getInterceptors(); + } + + List executedInterceptors = null; + Exception exception = null; + try { + if (CollectionUtils.isNotEmpty(interceptors)) { + executedInterceptors = new ArrayList<>(interceptors.size()); + for (StateHandlerInterceptor interceptor : interceptors) { + executedInterceptors.add(interceptor); + interceptor.preProcess(context); + } + } + + stateHandler.process(context); + + } catch (Exception e) { + exception = e; + throw e; + } finally { + if (CollectionUtils.isNotEmpty(executedInterceptors)) { + for (int i = executedInterceptors.size() - 1; i >= 0; i--) { + StateHandlerInterceptor interceptor = executedInterceptors.get(i); + interceptor.postProcess(context, exception); + } + } + } + } + + public void initDefaultHandlers() { + if (stateHandlers.isEmpty()) { + stateHandlers.put(DomainConstants.STATE_TYPE_SERVICE_TASK, new ServiceTaskStateHandler()); + + stateHandlers.put(DomainConstants.STATE_TYPE_SCRIPT_TASK, new ScriptTaskStateHandler()); + + stateHandlers.put(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION, new ServiceTaskStateHandler()); + + stateHandlers.put(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE, new SubStateMachineHandler()); + + stateHandlers.put(DomainConstants.STATE_TYPE_CHOICE, new ChoiceStateHandler()); + stateHandlers.put(DomainConstants.STATE_TYPE_SUCCEED, new SucceedEndStateHandler()); + stateHandlers.put(DomainConstants.STATE_TYPE_FAIL, new FailEndStateHandler()); + stateHandlers.put(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER, new CompensationTriggerStateHandler()); + stateHandlers.put(DomainConstants.STATE_TYPE_LOOP_START, new LoopStartStateHandler()); + } + } + + public Map getStateHandlers() { + return stateHandlers; + } + + public void setStateHandlers(Map stateHandlers) { + this.stateHandlers.putAll(stateHandlers); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessRouter.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessRouter.java new file mode 100644 index 0000000..b36081f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateMachineProcessRouter.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.pcext.routers.EndStateRouter; +import io.seata.saga.engine.pcext.routers.TaskStateRouter; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessRouter; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; + +/** + * StateMachine ProcessRouter + * + * @author lorne.cl + * @see ProcessRouter + */ +public class StateMachineProcessRouter implements ProcessRouter { + + private final Map stateRouters = new ConcurrentHashMap<>(); + + @Override + public Instruction route(ProcessContext context) throws FrameworkException { + + StateInstruction stateInstruction = context.getInstruction(StateInstruction.class); + + State state; + if (stateInstruction.getTemporaryState() != null) { + state = stateInstruction.getTemporaryState(); + stateInstruction.setTemporaryState(null); + } else { + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + StateMachine stateMachine = stateMachineConfig.getStateMachineRepository().getStateMachine( + stateInstruction.getStateMachineName(), stateInstruction.getTenantId()); + state = stateMachine.getStates().get(stateInstruction.getStateName()); + } + + String stateType = state.getType(); + + StateRouter router = stateRouters.get(stateType); + + Instruction instruction = null; + + List interceptors = null; + if (router instanceof InterceptableStateRouter) { + interceptors = ((InterceptableStateRouter)router).getInterceptors(); + } + + List executedInterceptors = null; + Exception exception = null; + try { + if (CollectionUtils.isNotEmpty(interceptors)) { + executedInterceptors = new ArrayList<>(interceptors.size()); + for (StateRouterInterceptor interceptor : interceptors) { + executedInterceptors.add(interceptor); + interceptor.preRoute(context, state); + } + } + + instruction = router.route(context, state); + + } catch (Exception e) { + exception = e; + throw e; + } finally { + if (CollectionUtils.isNotEmpty(executedInterceptors)) { + for (int i = executedInterceptors.size() - 1; i >= 0; i--) { + StateRouterInterceptor interceptor = executedInterceptors.get(i); + interceptor.postRoute(context, state, instruction, exception); + } + } + + //if 'Succeed' or 'Fail' State did not configured, we must end the state machine + if (instruction == null && !stateInstruction.isEnd()) { + EngineUtils.endStateMachine(context); + } + } + + return instruction; + } + + public void initDefaultStateRouters() { + if (this.stateRouters.isEmpty()) { + TaskStateRouter taskStateRouter = new TaskStateRouter(); + this.stateRouters.put(DomainConstants.STATE_TYPE_SERVICE_TASK, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_SCRIPT_TASK, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_CHOICE, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION, taskStateRouter); + this.stateRouters.put(DomainConstants.STATE_TYPE_LOOP_START, taskStateRouter); + + this.stateRouters.put(DomainConstants.STATE_TYPE_SUCCEED, new EndStateRouter()); + this.stateRouters.put(DomainConstants.STATE_TYPE_FAIL, new EndStateRouter()); + } + } + + public Map getStateRouters() { + return stateRouters; + } + + public void setStateRouters(Map stateRouters) { + this.stateRouters.putAll(stateRouters); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouter.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouter.java new file mode 100644 index 0000000..8f17817 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouter.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.State; + +/** + * StateRouter + * + * @author lorne.cl + */ +public interface StateRouter { + + Instruction route(ProcessContext context, State state) throws EngineExecutionException; +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouterInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouterInterceptor.java new file mode 100644 index 0000000..0a1b205 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/StateRouterInterceptor.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.State; + +/** + * StateRouter Interceptor + * + * @author lorne.cl + * @see StateRouter + */ +public interface StateRouterInterceptor { + + /** + * pre route + * + * @param context + * @param state + * @throws EngineExecutionException + */ + void preRoute(ProcessContext context, State state) throws EngineExecutionException; + + /** + * post route + * + * @param context + * @param state + * @param instruction + * @param e + * @throws EngineExecutionException + */ + void postRoute(ProcessContext context, State state, Instruction instruction, Exception e) + throws EngineExecutionException; + + + boolean match(Class clazz); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java new file mode 100644 index 0000000..1992513 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ChoiceStateHandler.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.evaluation.EvaluatorFactory; +import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.ChoiceState; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.ChoiceStateImpl; +import org.springframework.util.StringUtils; + +/** + * ChoiceState Handler + * + * @author lorne.cl + */ +public class ChoiceStateHandler implements StateHandler { + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ChoiceStateImpl choiceState = (ChoiceStateImpl)instruction.getState(context); + + Map choiceEvaluators = choiceState.getChoiceEvaluators(); + if (choiceEvaluators == null) { + synchronized (choiceState) { + choiceEvaluators = choiceState.getChoiceEvaluators(); + if (choiceEvaluators == null) { + + List choices = choiceState.getChoices(); + if (choices == null) { + choiceEvaluators = new LinkedHashMap<>(0); + } else { + choiceEvaluators = new LinkedHashMap<>(choices.size()); + for (ChoiceState.Choice choice : choices) { + Evaluator evaluator = getEvaluatorFactory(context).createEvaluator(choice.getExpression()); + choiceEvaluators.put(evaluator, choice.getNext()); + } + } + choiceState.setChoiceEvaluators(choiceEvaluators); + } + } + } + + Evaluator evaluator; + for (Map.Entry entry : choiceEvaluators.entrySet()) { + evaluator = (Evaluator)entry.getKey(); + if (evaluator.evaluate(context.getVariables())) { + context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, entry.getValue()); + return; + } + } + + if (StringUtils.isEmpty(choiceState.getDefault())) { + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(FrameworkErrorCode.StateMachineNoChoiceMatched, "No choice matched, maybe it is a bug. Choice state name: " + choiceState.getName(), stateMachineInstance, null); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + + context.setVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE, choiceState.getDefault()); + } + + public EvaluatorFactory getEvaluatorFactory(ProcessContext context) { + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + return stateMachineConfig.getEvaluatorFactoryManager().getEvaluatorFactory( + EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/CompensationTriggerStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/CompensationTriggerStateHandler.java new file mode 100644 index 0000000..fc2e6fd --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/CompensationTriggerStateHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.List; +import java.util.Stack; + +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.CompensationHolder; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * CompensationTriggerState Handler + * Start to execute compensation + * + * @author lorne.cl + */ +public class CompensationTriggerStateHandler implements StateHandler { + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + List stateInstanceList = stateMachineInstance.getStateList(); + if (CollectionUtils.isEmpty(stateInstanceList)) { + stateInstanceList = stateMachineConfig.getStateLogStore().queryStateInstanceListByMachineInstanceId( + stateMachineInstance.getId()); + } + + List stateListToBeCompensated = CompensationHolder.findStateInstListToBeCompensated(context, + stateInstanceList); + if (CollectionUtils.isNotEmpty(stateListToBeCompensated)) { + //Clear exceptions that occur during forward execution + Exception e = (Exception)context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + if (e != null) { + stateMachineInstance.setException(e); + } + + Stack stateStackToBeCompensated = CompensationHolder.getCurrent(context, true) + .getStateStackNeedCompensation(); + stateStackToBeCompensated.addAll(stateListToBeCompensated); + + //If the forward running state is empty or running, + // it indicates that the compensation state is automatically initiated in the state machine, + // and the forward state needs to be changed to the UN state. + //If the forward status is not the two states, then the compensation operation should be initiated by + // server recovery, + // and the forward state should not be modified. + if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals( + stateMachineInstance.getStatus())) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } + //Record the status of the state machine as "compensating", and the subsequent routing logic will route + // to the compensation state + stateMachineInstance.setCompensationStatus(ExecutionStatus.RU); + context.setVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE, instruction.getState(context)); + } else { + EngineUtils.endStateMachine(context); + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/FailEndStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/FailEndStateHandler.java new file mode 100644 index 0000000..19249eb --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/FailEndStateHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.Map; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.FailEndState; + +/** + * FailEndState Handler + * + * @author lorne.cl + */ +public class FailEndStateHandler implements StateHandler { + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + context.setVariable(DomainConstants.VAR_NAME_FAIL_END_STATE_FLAG, true); + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + FailEndState state = (FailEndState)instruction.getState(context); + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + contextVariables.put(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_CODE, state.getErrorCode()); + contextVariables.put(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_MSG, state.getMessage()); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/LoopStartStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/LoopStartStateHandler.java new file mode 100644 index 0000000..cdfebf3 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/LoopStartStateHandler.java @@ -0,0 +1,165 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.LoopContextHolder; +import io.seata.saga.engine.pcext.utils.LoopTaskUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.impl.ProcessContextImpl; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.TaskState.Loop; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loop State Handler + * Start Loop Execution + * + * @author anselleeyy + */ +public class LoopStartStateHandler implements StateHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(LoopStartStateHandler.class); + private static final int AWAIT_TIMEOUT = 1000; + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + instruction.setTemporaryState(null); + + Loop loop = LoopTaskUtils.getLoopConfig(context, instruction.getState(context)); + LoopContextHolder loopContextHolder = LoopContextHolder.getCurrent(context, true); + Semaphore semaphore = null; + int maxInstances = 0; + List loopContextList = new ArrayList<>(); + + if (null != loop) { + + if (!stateMachineConfig.isEnableAsync() || null == stateMachineConfig.getAsyncProcessCtrlEventPublisher()) { + throw new EngineExecutionException( + "Asynchronous start is disabled. Loop execution will run asynchronous, please set " + + "StateMachineConfig.enableAsync=true first.", FrameworkErrorCode.AsynchronousStartDisabled); + } + + int totalInstances; + if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME))) { + LoopTaskUtils.reloadLoopContext(context, instruction.getState(context).getName()); + totalInstances = loopContextHolder.getNrOfInstances().get() - loopContextHolder.getNrOfCompletedInstances().get(); + } else { + LoopTaskUtils.createLoopCounterContext(context); + totalInstances = loopContextHolder.getNrOfInstances().get(); + } + maxInstances = Math.min(loop.getParallel(), totalInstances); + semaphore = new Semaphore(maxInstances); + context.setVariable(DomainConstants.LOOP_SEMAPHORE, semaphore); + context.setVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE, true); + + // publish loop tasks + for (int i = 0; i < totalInstances; i++) { + try { + semaphore.acquire(); + + ProcessContextImpl tempContext; + // fail end inst should be forward without completion condition check + if (!loopContextHolder.getForwardCounterStack().isEmpty()) { + int failEndLoopCounter = loopContextHolder.getForwardCounterStack().pop(); + tempContext = (ProcessContextImpl)LoopTaskUtils.createLoopEventContext(context, failEndLoopCounter); + } else if (loopContextHolder.isFailEnd() || LoopTaskUtils.isCompletionConditionSatisfied(context)) { + semaphore.release(); + break; + } else { + tempContext = (ProcessContextImpl)LoopTaskUtils.createLoopEventContext(context, -1); + } + + if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME))) { + ((HierarchicalProcessContext)context).setVariableLocally( + DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD, LoopTaskUtils.isForSubStateMachineForward(tempContext)); + } + stateMachineConfig.getAsyncProcessCtrlEventPublisher().publish(tempContext); + loopContextHolder.getNrOfActiveInstances().incrementAndGet(); + loopContextList.add(tempContext); + } catch (InterruptedException e) { + LOGGER.error("try execute loop task for State: [{}] is interrupted, message: [{}]", + instruction.getStateName(), e.getMessage()); + throw new EngineExecutionException(e); + } + } + } else { + LOGGER.warn("Loop config of State [{}] is illegal, will execute as normal", instruction.getStateName()); + instruction.setTemporaryState(instruction.getState(context)); + } + + try { + if (null != semaphore) { + boolean isFinished = false; + while (!isFinished) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("wait {}ms for loop state [{}] finish", AWAIT_TIMEOUT, instruction.getStateName()); + } + isFinished = semaphore.tryAcquire(maxInstances, AWAIT_TIMEOUT, TimeUnit.MILLISECONDS); + } + + if (loopContextList.size() > 0) { + LoopTaskUtils.putContextToParent(context, loopContextList, instruction.getState(context)); + } + } + } catch (InterruptedException e) { + LOGGER.error("State: [{}] wait loop execution complete is interrupted, message: [{}]", + instruction.getStateName(), e.getMessage()); + throw new EngineExecutionException(e); + } finally { + context.removeVariable(DomainConstants.LOOP_SEMAPHORE); + context.removeVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE); + LoopContextHolder.clearCurrent(context); + } + + if (loopContextHolder.isFailEnd()) { + String currentExceptionRoute = LoopTaskUtils.decideCurrentExceptionRoute(loopContextList, stateMachineInstance.getStateMachine()); + if (StringUtils.isNotBlank(currentExceptionRoute)) { + ((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE, currentExceptionRoute); + } else { + for (ProcessContext processContext : loopContextList) { + if (processContext.hasVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION)) { + Exception exception = (Exception)processContext.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + EngineUtils.failStateMachine(context, exception); + break; + } + } + } + } + + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ScriptTaskStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ScriptTaskStateHandler.java new file mode 100644 index 0000000..b0ab8e4 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ScriptTaskStateHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.impl.ScriptTaskStateImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.script.Bindings; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.SimpleBindings; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ScriptTaskState Handler + * + * @author lorne.cl + */ +public class ScriptTaskStateHandler implements StateHandler, InterceptableStateHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScriptTaskStateHandler.class); + + private List interceptors = new ArrayList<>(); + + private volatile Map scriptEngineCache = new ConcurrentHashMap<>(); + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ScriptTaskStateImpl state = (ScriptTaskStateImpl) instruction.getState(context); + + String scriptType = state.getScriptType(); + String scriptContent = state.getScriptContent(); + + Object result; + try { + List input = (List) context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute ScriptTaskState[{}], ScriptType[{}], Input:{}", + state.getName(), scriptType, input); + } + + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + ScriptEngine scriptEngine = getScriptEngineFromCache(scriptType, stateMachineConfig.getScriptEngineManager()); + if (scriptEngine == null) { + throw new EngineExecutionException("No such ScriptType[" + scriptType + "]", + FrameworkErrorCode.ObjectNotExists); + } + + Bindings bindings = null; + Map inputMap = null; + if (CollectionUtils.isNotEmpty(input) && input.get(0) instanceof Map) { + inputMap = (Map) input.get(0); + } + List inputExps = state.getInput(); + if (CollectionUtils.isNotEmpty(inputExps) && inputExps.get(0) instanceof Map) { + Map inputExpMap = (Map) inputExps.get(0); + if (inputExpMap.size() > 0) { + bindings = new SimpleBindings(); + for (String property : inputExpMap.keySet()) { + if (inputMap != null && inputMap.containsKey(property)) { + bindings.put(property, inputMap.get(property)); + } else { + //if we do not bind the null value property, groovy will throw MissingPropertyException + bindings.put(property, null); + } + } + } + } + if (bindings != null) { + result = scriptEngine.eval(scriptContent, bindings); + } + else { + result = scriptEngine.eval(scriptContent); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("<<<<<<<<<<<<<<<<<<<<<< ScriptTaskState[{}], ScriptType[{}], Execute finish. result: {}", + state.getName(), scriptType, result); + } + + if (result != null) { + ((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_OUTPUT_PARAMS, + result); + } + + } catch (Throwable e) { + + LOGGER.error("<<<<<<<<<<<<<<<<<<<<<< ScriptTaskState[{}], ScriptTaskState[{}] Execute failed.", + state.getName(), scriptType, e); + + ((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e); + + EngineUtils.handleException(context, state, e); + } + + } + + protected ScriptEngine getScriptEngineFromCache(String scriptType, ScriptEngineManager scriptEngineManager) { + return CollectionUtils.computeIfAbsent(scriptEngineCache, scriptType, + key -> scriptEngineManager.getEngineByName(scriptType)); + } + + @Override + public List getInterceptors() { + return interceptors; + } + + @Override + public void addInterceptor(StateHandlerInterceptor interceptor) { + if (interceptors != null && !interceptors.contains(interceptor)) { + interceptors.add(interceptor); + } + } + + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ServiceTaskStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ServiceTaskStateHandler.java new file mode 100644 index 0000000..7d84242 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/ServiceTaskStateHandler.java @@ -0,0 +1,191 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.invoker.ServiceInvoker; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.CompensateSubStateMachineState; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.ServiceTaskState; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContextAware; +import org.springframework.util.StringUtils; + +/** + * ServiceTaskState Handler + * + * @author lorne.cl + */ +public class ServiceTaskStateHandler implements StateHandler, InterceptableStateHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTaskStateHandler.class); + + private List interceptors = new ArrayList<>(); + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ServiceTaskStateImpl state = (ServiceTaskStateImpl) instruction.getState(context); + + String serviceName = state.getServiceName(); + String methodName = state.getServiceMethod(); + StateInstance stateInstance = (StateInstance) context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + + Object result; + try { + + List input = (List) context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS); + + //Set the current task execution status to RU (Running) + stateInstance.setStatus(ExecutionStatus.RU); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute State[{}], ServiceName[{}], Method[{}], Input:{}", + state.getName(), serviceName, methodName, input); + } + + if (state instanceof CompensateSubStateMachineState) { + //If it is the compensation of the substate machine, + // directly call the state machine's compensate method + result = compensateSubStateMachine(context, state, input, stateInstance, + (StateMachineEngine) context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_ENGINE)); + } else { + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + ServiceInvoker serviceInvoker = stateMachineConfig.getServiceInvokerManager().getServiceInvoker( + state.getServiceType()); + if (serviceInvoker == null) { + throw new EngineExecutionException("No such ServiceInvoker[" + state.getServiceType() + "]", + FrameworkErrorCode.ObjectNotExists); + } + if (serviceInvoker instanceof ApplicationContextAware) { + ((ApplicationContextAware) serviceInvoker).setApplicationContext( + stateMachineConfig.getApplicationContext()); + } + + result = serviceInvoker.invoke(state, input.toArray()); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("<<<<<<<<<<<<<<<<<<<<<< State[{}], ServiceName[{}], Method[{}] Execute finish. result: {}", + state.getName(), serviceName, methodName, result); + } + + if (result != null) { + stateInstance.setOutputParams(result); + ((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_OUTPUT_PARAMS, + result); + } + + } catch (Throwable e) { + + LOGGER.error("<<<<<<<<<<<<<<<<<<<<<< State[{}], ServiceName[{}], Method[{}] Execute failed.", + state.getName(), serviceName, methodName, e); + + ((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e); + + EngineUtils.handleException(context, state, e); + } + + } + + private Object compensateSubStateMachine(ProcessContext context, ServiceTaskState state, Object input, + StateInstance stateInstance, StateMachineEngine engine) { + + String subStateMachineParentId = (String) context.getVariable( + state.getName() + DomainConstants.VAR_NAME_SUB_MACHINE_PARENT_ID); + if (StringUtils.isEmpty(subStateMachineParentId)) { + throw new EngineExecutionException("sub statemachine parentId is required", + FrameworkErrorCode.ObjectNotExists); + } + + StateMachineConfig stateMachineConfig = (StateMachineConfig) context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + List subInst = stateMachineConfig.getStateLogStore().queryStateMachineInstanceByParentId( + subStateMachineParentId); + if (CollectionUtils.isEmpty(subInst)) { + throw new EngineExecutionException( + "cannot find sub statemachine instance by parentId:" + subStateMachineParentId, + FrameworkErrorCode.ObjectNotExists); + } + + String subStateMachineInstId = subInst.get(0).getId(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to compensate sub statemachine [id:{}]", subStateMachineInstId); + } + + Map startParams = new HashMap<>(0); + if (input instanceof List) { + List listInputParams = (List) input; + if (listInputParams.size() > 0) { + startParams = (Map) listInputParams.get(0); + } + } else if (input instanceof Map) { + startParams = (Map) input; + } + + StateMachineInstance compensateInst = engine.compensate(subStateMachineInstId, startParams); + stateInstance.setStatus(compensateInst.getCompensationStatus()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "<<<<<<<<<<<<<<<<<<<<<< Compensate sub statemachine [id:{}] finished with status[{}], " + + "compensateState[{}]", + subStateMachineInstId, compensateInst.getStatus(), compensateInst.getCompensationStatus()); + } + return compensateInst.getEndParams(); + } + + @Override + public List getInterceptors() { + return interceptors; + } + + @Override + public void addInterceptor(StateHandlerInterceptor interceptor) { + if (interceptors != null && !interceptors.contains(interceptor)) { + interceptors.add(interceptor); + } + } + + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SubStateMachineHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SubStateMachineHandler.java new file mode 100644 index 0000000..00e6506 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SubStateMachineHandler.java @@ -0,0 +1,213 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.SubStateMachine; +import io.seata.saga.statelang.domain.impl.SubStateMachineImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * SubStateMachine Handler + * + * @author lorne.cl + */ +public class SubStateMachineHandler implements StateHandler, InterceptableStateHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubStateMachineHandler.class); + + private List interceptors = new ArrayList<>(); + + private static ExecutionStatus decideStatus(StateMachineInstance stateMachineInstance, boolean isForward) { + + if (isForward && ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) { + return ExecutionStatus.SU; + } else if (stateMachineInstance.getCompensationStatus() == null || ExecutionStatus.FA.equals( + stateMachineInstance.getCompensationStatus())) { + return stateMachineInstance.getStatus(); + } else if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) { + return ExecutionStatus.FA; + } else { + return ExecutionStatus.UN; + } + } + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + SubStateMachineImpl subStateMachine = (SubStateMachineImpl)instruction.getState(context); + + StateMachineEngine engine = (StateMachineEngine)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_ENGINE); + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + + Object inputParamsObj = context.getVariable(DomainConstants.VAR_NAME_INPUT_PARAMS); + Map startParams = new HashMap<>(0); + if (inputParamsObj instanceof List) { + List listInputParams = (List)inputParamsObj; + if (listInputParams.size() > 0) { + startParams = (Map)listInputParams.get(0); + } + } else if (inputParamsObj instanceof Map) { + startParams = (Map)inputParamsObj; + } + + startParams.put(DomainConstants.VAR_NAME_PARENT_ID, EngineUtils.generateParentId(stateInstance)); + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(">>>>>>>>>>>>>>>>>>>>>> Start to execute SubStateMachine [{}] by state[{}]", + subStateMachine.getStateMachineName(), subStateMachine.getName()); + } + StateMachineInstance subStateMachineInstance = callSubStateMachine(startParams, engine, context, + stateInstance, subStateMachine); + + Map outputParams = subStateMachineInstance.getEndParams(); + boolean isForward = DomainConstants.OPERATION_NAME_FORWARD.equals( + context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME)); + ExecutionStatus callSubMachineStatus = decideStatus(subStateMachineInstance, isForward); + stateInstance.setStatus(callSubMachineStatus); + outputParams.put(DomainConstants.VAR_NAME_SUB_STATEMACHINE_EXEC_STATUE, callSubMachineStatus.toString()); + context.setVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS, outputParams); + stateInstance.setOutputParams(outputParams); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "<<<<<<<<<<<<<<<<<<<<<< SubStateMachine[{}] execute finish with status[{}], compensateStatus[{}]", + subStateMachine.getStateMachineName(), subStateMachineInstance.getStatus(), + subStateMachineInstance.getCompensationStatus()); + } + + } catch (Exception e) { + + LOGGER.error("SubStateMachine[{}] execute failed by state[name:{}]", subStateMachine.getStateMachineName(), + subStateMachine.getName(), e); + + if (e instanceof ForwardInvalidException) { + + String retriedId = stateInstance.getStateIdRetriedFor(); + StateInstance stateToBeRetried = null; + for (StateInstance stateInst : stateMachineInstance.getStateList()) { + if (retriedId.equals(stateInst.getId())) { + stateToBeRetried = stateInst; + break; + } + } + if (stateToBeRetried != null) { + stateInstance.setStatus(stateToBeRetried.getStatus()); + } + } + + context.setVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION, e); + + EngineUtils.handleException(context, subStateMachine, e); + } + } + + private StateMachineInstance callSubStateMachine(Map startParams, StateMachineEngine engine, + ProcessContext context, StateInstance stateInstance, + SubStateMachine subStateMachine) { + if (!Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD))) { + return startNewStateMachine(startParams, engine, stateInstance, subStateMachine); + } else { + context.removeVariable(DomainConstants.VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD); + + return forwardStateMachine(startParams, engine, context, stateInstance, subStateMachine); + } + } + + private StateMachineInstance startNewStateMachine(Map startParams, StateMachineEngine engine, + StateInstance stateInstance, SubStateMachine subStateMachine) { + + StateMachineInstance subStateMachineInstance; + if (stateInstance.getBusinessKey() != null) { + subStateMachineInstance = engine.startWithBusinessKey(subStateMachine.getStateMachineName(), + stateInstance.getStateMachineInstance().getTenantId(), stateInstance.getBusinessKey(), startParams); + } else { + subStateMachineInstance = engine.start(subStateMachine.getStateMachineName(), + stateInstance.getStateMachineInstance().getTenantId(), startParams); + } + return subStateMachineInstance; + } + + private StateMachineInstance forwardStateMachine(Map startParams, StateMachineEngine engine, + ProcessContext context, StateInstance stateInstance, + SubStateMachine subStateMachine) { + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + StateLogStore statePersister = stateMachineConfig.getStateLogStore(); + if (statePersister == null) { + throw new ForwardInvalidException("StatePersister is not configured", FrameworkErrorCode.ObjectNotExists); + } + + StateInstance originalStateInst = stateInstance; + do { + originalStateInst = statePersister.getStateInstance(originalStateInst.getStateIdRetriedFor(), + originalStateInst.getMachineInstanceId()); + } while (StringUtils.hasText(originalStateInst.getStateIdRetriedFor())); + + List subInst = statePersister.queryStateMachineInstanceByParentId( + EngineUtils.generateParentId(originalStateInst)); + if (subInst.size() > 0) { + String subInstId = subInst.get(0).getId(); + + return engine.forward(subInstId, startParams); + } else { + originalStateInst.setStateMachineInstance(stateInstance.getStateMachineInstance()); + return startNewStateMachine(startParams, engine, originalStateInst, subStateMachine); + } + } + + @Override + public List getInterceptors() { + return interceptors; + } + + @Override + public void addInterceptor(StateHandlerInterceptor interceptor) { + if (interceptors != null && !interceptors.contains(interceptor)) { + interceptors.add(interceptor); + } + } + + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SucceedEndStateHandler.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SucceedEndStateHandler.java new file mode 100644 index 0000000..6da4d16 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/handlers/SucceedEndStateHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.handlers; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateHandler; +import io.seata.saga.proctrl.ProcessContext; + +/** + * SucceedEndState Handler + * + * @author lorne.cl + */ +public class SucceedEndStateHandler implements StateHandler { + + @Override + public void process(ProcessContext context) throws EngineExecutionException { + //Do Nothing + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/EndStateRouterInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/EndStateRouterInterceptor.java new file mode 100644 index 0000000..d9b9674 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/EndStateRouterInterceptor.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.interceptors; + +import io.seata.common.loader.LoadLevel; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateRouter; +import io.seata.saga.engine.pcext.StateRouterInterceptor; +import io.seata.saga.engine.pcext.routers.EndStateRouter; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.State; + +/** + * EndStateRouter Interceptor + * + * @author lorne.cl + */ +@LoadLevel(name = "EndState", order = 100) +public class EndStateRouterInterceptor implements StateRouterInterceptor { + + @Override + public void preRoute(ProcessContext context, State state) throws EngineExecutionException { + //Do Nothing + } + + @Override + public void postRoute(ProcessContext context, State state, Instruction instruction, Exception e) + throws EngineExecutionException { + EngineUtils.endStateMachine(context); + } + + @Override + public boolean match(Class clazz) { + return clazz != null && EndStateRouter.class.isAssignableFrom(clazz); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/LoopTaskHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/LoopTaskHandlerInterceptor.java new file mode 100644 index 0000000..a21fda4 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/LoopTaskHandlerInterceptor.java @@ -0,0 +1,138 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.interceptors; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; + +import io.seata.common.loader.LoadLevel; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler; +import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler; +import io.seata.saga.engine.pcext.utils.CompensationHolder; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.LoopContextHolder; +import io.seata.saga.engine.pcext.utils.LoopTaskUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.TaskState.Loop; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; + +/** + * State Interceptor For ServiceTask, SubStateMachine, ScriptTask With Loop Attribute + * + * @author anselleeyy + */ +@LoadLevel(name = "LoopTask", order = 90) +public class LoopTaskHandlerInterceptor implements StateHandlerInterceptor { + + @Override + public boolean match(Class clazz) { + return clazz != null && + (ServiceTaskStateHandler.class.isAssignableFrom(clazz) + || SubStateMachineHandler.class.isAssignableFrom(clazz) + || ScriptTaskHandlerInterceptor.class.isAssignableFrom(clazz)); + } + + @Override + public void preProcess(ProcessContext context) throws EngineExecutionException { + + if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) { + StateInstruction instruction = context.getInstruction(StateInstruction.class); + AbstractTaskState currentState = (AbstractTaskState)instruction.getState(context); + + int loopCounter; + Loop loop; + + // get loop config + if (context.hasVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE)) { + // compensate condition should get stateToBeCompensated 's config + CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true); + StateInstance stateToBeCompensated = compensationHolder.getStatesNeedCompensation().get(currentState.getName()); + AbstractTaskState compensateState = (AbstractTaskState)stateToBeCompensated.getStateMachineInstance() + .getStateMachine().getState(EngineUtils.getOriginStateName(stateToBeCompensated)); + loop = compensateState.getLoop(); + loopCounter = LoopTaskUtils.reloadLoopCounter(stateToBeCompensated.getName()); + } else { + loop = currentState.getLoop(); + loopCounter = (int)context.getVariable(DomainConstants.LOOP_COUNTER); + } + + Collection collection = LoopContextHolder.getCurrent(context, true).getCollection(); + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + Map copyContextVariables = new ConcurrentHashMap<>(contextVariables); + copyContextVariables.put(loop.getElementIndexName(), loopCounter); + copyContextVariables.put(loop.getElementVariableName(), iterator(collection, loopCounter)); + ((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, copyContextVariables); + } + } + + @Override + public void postProcess(ProcessContext context, Exception e) throws EngineExecutionException { + + if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) { + + StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + if (null != stateInstance && !LoopContextHolder.getCurrent(context, true).isFailEnd()) { + if (!ExecutionStatus.SU.equals(stateInstance.getStatus())) { + LoopContextHolder.getCurrent(context, true).setFailEnd(true); + } + } + + Exception exp = (Exception)((HierarchicalProcessContext)context).getVariableLocally(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + if (exp == null) { + exp = e; + } + + if (null != e) { + if (context.hasVariable(DomainConstants.LOOP_SEMAPHORE)) { + Semaphore semaphore = (Semaphore)context.getVariable(DomainConstants.LOOP_SEMAPHORE); + semaphore.release(); + } + } + + if (null != exp) { + LoopContextHolder.getCurrent(context, true).setFailEnd(true); + } else { + LoopContextHolder.getCurrent(context, true).getNrOfCompletedInstances().incrementAndGet(); + } + LoopContextHolder.getCurrent(context, true).getNrOfActiveInstances().decrementAndGet(); + + } + } + + private Object iterator(Collection collection, int loopCounter) { + Iterator iterator = collection.iterator(); + int index = 0; + Object value = null; + while (index <= loopCounter) { + value = iterator.next(); + index += 1; + } + return value; + } + +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java new file mode 100644 index 0000000..9e26796 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ScriptTaskHandlerInterceptor.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.interceptors; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.ParameterUtils; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.ScriptTaskStateImpl; + +import java.util.List; +import java.util.Map; + +/** + * StateInterceptor for ScriptTask + * + * @author lorne.cl + */ +@LoadLevel(name = "ScriptTask", order = 100) +public class ScriptTaskHandlerInterceptor implements StateHandlerInterceptor { + + @Override + public boolean match(Class clazz) { + return clazz != null && + ScriptTaskStateHandler.class.isAssignableFrom(clazz); + } + + @Override + public void preProcess(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + ScriptTaskStateImpl state = (ScriptTaskStateImpl)instruction.getState(context); + List serviceInputParams = null; + if (contextVariables != null) { + try { + serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), null, + state, contextVariables); + } catch (Exception e) { + + String message = "Task [" + state.getName() + + "] input parameters assign failed, please check 'Input' expression:" + e.getMessage(); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e, + FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName()); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + + ((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_INPUT_PARAMS, + serviceInputParams); + } + + @Override + public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ScriptTaskStateImpl state = (ScriptTaskStateImpl)instruction.getState(context); + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (exp == null) { + exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + } + + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + Object serviceOutputParams = context.getVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + if (serviceOutputParams != null) { + try { + Map outputVariablesToContext = ParameterUtils.createOutputParams( + stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams); + if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { + contextVariables.putAll(outputVariablesToContext); + } + } catch (Exception e) { + String message = "Task [" + state.getName() + + "] output parameters assign failed, please check 'Output' expression:" + e.getMessage(); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e, + FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName()); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + + context.removeVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + context.removeVariable(DomainConstants.VAR_NAME_INPUT_PARAMS); + + if (exp != null && context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH) != null + && (Boolean)context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH)) { + //If there is an exception and there is no catch, need to exit the state machine to execute. + + context.removeVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH); + EngineUtils.failStateMachine(context, exp); + } + + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java new file mode 100644 index 0000000..075e3d6 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/interceptors/ServiceTaskHandlerInterceptor.java @@ -0,0 +1,433 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.interceptors; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.loader.LoadLevel; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.evaluation.Evaluator; +import io.seata.saga.engine.evaluation.EvaluatorFactory; +import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; +import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateHandler; +import io.seata.saga.engine.pcext.StateHandlerInterceptor; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.handlers.ServiceTaskStateHandler; +import io.seata.saga.engine.pcext.handlers.SubStateMachineHandler; +import io.seata.saga.engine.pcext.utils.CompensationHolder; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.LoopTaskUtils; +import io.seata.saga.engine.pcext.utils.ParameterUtils; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import io.seata.saga.statelang.domain.impl.StateInstanceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * StateInterceptor for ServiceTask, SubStateMachine, CompensateState + * + * @author lorne.cl + */ +@LoadLevel(name = "ServiceTask", order = 100) +public class ServiceTaskHandlerInterceptor implements StateHandlerInterceptor { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTaskHandlerInterceptor.class); + + @Override + public boolean match(Class clazz) { + return clazz != null && + (ServiceTaskStateHandler.class.isAssignableFrom(clazz) + || SubStateMachineHandler.class.isAssignableFrom(clazz)); + } + + @Override + public void preProcess(ProcessContext context) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (EngineUtils.isTimeout(stateMachineInstance.getGmtUpdated(), stateMachineConfig.getTransOperationTimeout())) { + String message = "Saga Transaction [stateMachineInstanceId:" + stateMachineInstance.getId() + + "] has timed out, stop execution now."; + + LOGGER.error(message); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(null, + FrameworkErrorCode.StateMachineExecutionTimeout, message, stateMachineInstance, instruction.getStateName()); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + + StateInstanceImpl stateInstance = new StateInstanceImpl(); + + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context); + List serviceInputParams = null; + if (contextVariables != null) { + try { + serviceInputParams = ParameterUtils.createInputParams(stateMachineConfig.getExpressionFactoryManager(), stateInstance, + state, contextVariables); + } catch (Exception e) { + + String message = "Task [" + state.getName() + + "] input parameters assign failed, please check 'Input' expression:" + e.getMessage(); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e, + FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, state.getName()); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + + ((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_INPUT_PARAMS, + serviceInputParams); + + stateInstance.setMachineInstanceId(stateMachineInstance.getId()); + stateInstance.setStateMachineInstance(stateMachineInstance); + Object isForCompensation = state.isForCompensation(); + if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE) && !Boolean.TRUE.equals(isForCompensation)) { + stateInstance.setName(LoopTaskUtils.generateLoopStateName(context, state.getName())); + StateInstance lastRetriedStateInstance = LoopTaskUtils.findOutLastRetriedStateInstance(stateMachineInstance, + stateInstance.getName()); + stateInstance.setStateIdRetriedFor( + lastRetriedStateInstance == null ? null : lastRetriedStateInstance.getId()); + } else { + stateInstance.setName(state.getName()); + stateInstance.setStateIdRetriedFor( + (String)context.getVariable(state.getName() + DomainConstants.VAR_NAME_RETRIED_STATE_INST_ID)); + } + stateInstance.setGmtStarted(new Date()); + stateInstance.setGmtUpdated(stateInstance.getGmtStarted()); + stateInstance.setStatus(ExecutionStatus.RU); + + if (StringUtils.hasLength(stateInstance.getBusinessKey())) { + + ((Map)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT)).put( + state.getName() + DomainConstants.VAR_NAME_BUSINESSKEY, stateInstance.getBusinessKey()); + } + + stateInstance.setType(state.getType()); + + stateInstance.setForUpdate(state.isForUpdate()); + stateInstance.setServiceName(state.getServiceName()); + stateInstance.setServiceMethod(state.getServiceMethod()); + stateInstance.setServiceType(state.getServiceType()); + + if (isForCompensation != null && (Boolean)isForCompensation) { + CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true); + StateInstance stateToBeCompensated = compensationHolder.getStatesNeedCompensation().get(state.getName()); + if (stateToBeCompensated != null) { + + stateToBeCompensated.setCompensationState(stateInstance); + stateInstance.setStateIdCompensatedFor(stateToBeCompensated.getId()); + } else { + LOGGER.error("Compensation State[{}] has no state to compensate, maybe this is a bug.", + state.getName()); + } + CompensationHolder.getCurrent(context, true).addForCompensationState(stateInstance.getName(), + stateInstance); + } + + if (DomainConstants.OPERATION_NAME_FORWARD.equals(context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME)) + && StringUtils.isEmpty(stateInstance.getStateIdRetriedFor()) && !state.isForCompensation()) { + + List stateList = stateMachineInstance.getStateList(); + if (CollectionUtils.isNotEmpty(stateList)) { + for (int i = stateList.size() - 1; i >= 0; i--) { + StateInstance executedState = stateList.get(i); + + if (stateInstance.getName().equals(executedState.getName())) { + stateInstance.setStateIdRetriedFor(executedState.getId()); + executedState.setIgnoreStatus(true); + break; + } + } + } + } + + stateInstance.setInputParams(serviceInputParams); + + if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist() + && stateMachineConfig.getStateLogStore() != null) { + + try { + stateMachineConfig.getStateLogStore().recordStateStarted(stateInstance, context); + } catch (Exception e) { + + String message = "Record state[" + state.getName() + "] started failed, stateMachineInstance[" + stateMachineInstance + .getId() + "], Reason: " + e.getMessage(); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e, + FrameworkErrorCode.ExceptionCaught, message, stateMachineInstance, state.getName()); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + + if (StringUtils.isEmpty(stateInstance.getId())) { + stateInstance.setId(stateMachineConfig.getSeqGenerator().generate(DomainConstants.SEQ_ENTITY_STATE_INST)); + } + stateMachineInstance.putStateInstance(stateInstance.getId(), stateInstance); + ((HierarchicalProcessContext)context).setVariableLocally(DomainConstants.VAR_NAME_STATE_INST, stateInstance); + } + + @Override + public void postProcess(ProcessContext context, Exception exp) throws EngineExecutionException { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + ServiceTaskStateImpl state = (ServiceTaskStateImpl)instruction.getState(context); + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + if (stateInstance == null || !stateMachineInstance.isRunning()) { + LOGGER.warn("StateMachineInstance[id:" + stateMachineInstance.getId() + "] is end. stop running"); + return; + } + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (exp == null) { + exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + } + stateInstance.setException(exp); + + decideExecutionStatus(context, stateInstance, state, exp); + + if (ExecutionStatus.SU.equals(stateInstance.getStatus()) && exp != null) { + + if (LOGGER.isInfoEnabled()) { + LOGGER.info( + "Although an exception occurs, the execution status map to SU, and the exception is ignored when " + + "the execution status decision."); + } + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + } + + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + Object serviceOutputParams = context.getVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + if (serviceOutputParams != null) { + try { + Map outputVariablesToContext = ParameterUtils.createOutputParams( + stateMachineConfig.getExpressionFactoryManager(), state, serviceOutputParams); + if (CollectionUtils.isNotEmpty(outputVariablesToContext)) { + contextVariables.putAll(outputVariablesToContext); + } + } catch (Exception e) { + String message = "Task [" + state.getName() + + "] output parameters assign failed, please check 'Output' expression:" + e.getMessage(); + + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException(e, + FrameworkErrorCode.VariablesAssignError, message, stateMachineInstance, stateInstance); + + if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist() + && stateMachineConfig.getStateLogStore() != null) { + + stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context); + } + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + + context.removeVariable(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + context.removeVariable(DomainConstants.VAR_NAME_INPUT_PARAMS); + + stateInstance.setGmtEnd(new Date()); + + if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist() + && stateMachineConfig.getStateLogStore() != null) { + stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context); + } + + if (exp != null && context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH) != null + && (Boolean)context.getVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH)) { + //If there is an exception and there is no catch, need to exit the state machine to execute. + + context.removeVariable(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH); + EngineUtils.failStateMachine(context, exp); + } + + } + + private void decideExecutionStatus(ProcessContext context, StateInstance stateInstance, ServiceTaskStateImpl state, + Exception exp) { + Map statusMatchList = state.getStatus(); + if (CollectionUtils.isNotEmpty(statusMatchList)) { + if (state.isAsync()) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn( + "Service[{}.{}] is execute asynchronously, null return value collected, so user defined " + + "Status Matching skipped. stateName: {}, branchId: {}", state.getServiceName(), + state.getServiceMethod(), state.getName(), stateInstance.getId()); + } + } else { + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + Map statusEvaluators = state.getStatusEvaluators(); + if (statusEvaluators == null) { + synchronized (state) { + statusEvaluators = state.getStatusEvaluators(); + if (statusEvaluators == null) { + statusEvaluators = new LinkedHashMap<>(statusMatchList.size()); + String expressionStr, statusVal; + Evaluator evaluator; + for (Map.Entry entry : statusMatchList.entrySet()) { + expressionStr = entry.getKey(); + statusVal = entry.getValue(); + evaluator = createEvaluator(stateMachineConfig.getEvaluatorFactoryManager(), expressionStr); + if (evaluator != null) { + statusEvaluators.put(evaluator, statusVal); + } + } + } + state.setStatusEvaluators(statusEvaluators); + } + } + + for (Object evaluatorObj : statusEvaluators.keySet()) { + Evaluator evaluator = (Evaluator)evaluatorObj; + String statusVal = statusEvaluators.get(evaluator); + if (evaluator.evaluate(context.getVariables())) { + stateInstance.setStatus(ExecutionStatus.valueOf(statusVal)); + break; + } + } + + if (exp == null && (stateInstance.getStatus() == null || ExecutionStatus.RU.equals( + stateInstance.getStatus()))) { + + if (state.isForUpdate()) { + stateInstance.setStatus(ExecutionStatus.UN); + } else { + stateInstance.setStatus(ExecutionStatus.FA); + } + stateInstance.setGmtEnd(new Date()); + + StateMachineInstance stateMachineInstance = stateInstance.getStateMachineInstance(); + + if (stateMachineInstance.getStateMachine().isPersist() && state.isPersist() + && stateMachineConfig.getStateLogStore() != null) { + stateMachineConfig.getStateLogStore().recordStateFinished(stateInstance, context); + } + + EngineExecutionException exception = new EngineExecutionException("State [" + state.getName() + + "] execute finished, but cannot matching status, pls check its status manually", + FrameworkErrorCode.NoMatchedStatus); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("State[{}] execute finish with status[{}]", state.getName(), + stateInstance.getStatus()); + } + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + } + } + + if (stateInstance.getStatus() == null || ExecutionStatus.RU.equals(stateInstance.getStatus())) { + + if (exp == null) { + stateInstance.setStatus(ExecutionStatus.SU); + } else { + if (state.isForUpdate() || state.isForCompensation()) { + + stateInstance.setStatus(ExecutionStatus.UN); + ExceptionUtils.NetExceptionType t = ExceptionUtils.getNetExceptionType(exp); + if (t != null) { + if (t.equals(ExceptionUtils.NetExceptionType.CONNECT_EXCEPTION)) { + stateInstance.setStatus(ExecutionStatus.FA); + } else if (t.equals(ExceptionUtils.NetExceptionType.READ_TIMEOUT_EXCEPTION)) { + stateInstance.setStatus(ExecutionStatus.UN); + } + } else { + stateInstance.setStatus(ExecutionStatus.UN); + } + } else { + stateInstance.setStatus(ExecutionStatus.FA); + } + } + } + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("State[{}] finish with status[{}]", state.getName(), stateInstance.getStatus()); + } + } + + private Evaluator createEvaluator(EvaluatorFactoryManager evaluatorFactoryManager, String expressionStr) { + String expressionType = null; + String expressionContent = null; + Evaluator evaluator = null; + if (StringUtils.hasLength(expressionStr)) { + if (expressionStr.startsWith("$")) { + int expTypeStart = expressionStr.indexOf("$"); + int expTypeEnd = expressionStr.indexOf("{", expTypeStart); + + if (expTypeStart >= 0 && expTypeEnd > expTypeStart) { + expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd); + } + + int expEnd = expressionStr.lastIndexOf("}"); + if (expTypeEnd > 0 && expEnd > expTypeEnd) { + expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd); + } + } else { + expressionContent = expressionStr; + } + + EvaluatorFactory evaluatorFactory = evaluatorFactoryManager.getEvaluatorFactory(expressionType); + if (evaluatorFactory == null) { + throw new IllegalArgumentException("Cannot get EvaluatorFactory by Type[" + expressionType + "]"); + } + evaluator = evaluatorFactory.createEvaluator(expressionContent); + if (evaluator instanceof ExpressionEvaluator) { + ((ExpressionEvaluator)evaluator).setRootObjectName(DomainConstants.VAR_NAME_OUTPUT_PARAMS); + } + } + return evaluator; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/EndStateRouter.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/EndStateRouter.java new file mode 100644 index 0000000..a3b8c2c --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/EndStateRouter.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.routers; + +import java.util.ArrayList; +import java.util.List; + +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.InterceptableStateRouter; +import io.seata.saga.engine.pcext.StateRouter; +import io.seata.saga.engine.pcext.StateRouterInterceptor; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.State; + +/** + * EndState Router + * + * @author lorne.cl + */ +public class EndStateRouter implements StateRouter, InterceptableStateRouter { + + private List interceptors = new ArrayList<>(); + + @Override + public Instruction route(ProcessContext context, State state) throws EngineExecutionException { + return null;//Return null to stop execution + } + + @Override + public List getInterceptors() { + return interceptors; + } + + @Override + public void addInterceptor(StateRouterInterceptor interceptor) { + if (interceptors != null && !interceptors.contains(interceptor)) { + interceptors.add(interceptor); + } + } + + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/TaskStateRouter.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/TaskStateRouter.java new file mode 100644 index 0000000..47c09e3 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/routers/TaskStateRouter.java @@ -0,0 +1,195 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.routers; + +import java.util.Stack; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.StateRouter; +import io.seata.saga.engine.pcext.utils.CompensationHolder; +import io.seata.saga.engine.pcext.utils.EngineUtils; +import io.seata.saga.engine.pcext.utils.LoopTaskUtils; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.CompensateSubStateMachineState; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.SubStateMachine; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import io.seata.saga.statelang.domain.impl.LoopStartStateImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +/** + * TaskState Router + * + * @author lorne.cl + */ +public class TaskStateRouter implements StateRouter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TaskStateRouter.class); + + @Override + public Instruction route(ProcessContext context, State state) throws EngineExecutionException { + + StateInstruction stateInstruction = context.getInstruction(StateInstruction.class); + if (stateInstruction.isEnd()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info( + "StateInstruction is ended, Stop the StateMachine executing. StateMachine[{}] Current State[{}]", + stateInstruction.getStateMachineName(), state.getName()); + } + return null; + } + + // check if in loop async condition + if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE))) { + return null; + } + + //The current CompensationTriggerState can mark the compensation process is started and perform compensation + // route processing. + State compensationTriggerState = (State)context.getVariable( + DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE); + if (compensationTriggerState != null) { + return compensateRoute(context, compensationTriggerState); + } + + //There is an exception route, indicating that an exception is thrown, and the exception route is prioritized. + String next = (String)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE); + + if (StringUtils.hasLength(next)) { + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE); + } else { + next = state.getNext(); + } + + //If next is empty, the state selected by the Choice state was taken. + if (!StringUtils.hasLength(next) && context.hasVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE)) { + next = (String)context.getVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE); + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_CHOICE); + } + + if (!StringUtils.hasLength(next)) { + return null; + } + + StateMachine stateMachine = state.getStateMachine(); + + State nextState = stateMachine.getState(next); + if (nextState == null) { + throw new EngineExecutionException("Next state[" + next + "] is not exits", + FrameworkErrorCode.ObjectNotExists); + } + + stateInstruction.setStateName(next); + + if (null != LoopTaskUtils.getLoopConfig(context, nextState)) { + stateInstruction.setTemporaryState(new LoopStartStateImpl()); + } + + return stateInstruction; + } + + private Instruction compensateRoute(ProcessContext context, State compensationTriggerState) { + + //If there is already a compensation state that has been executed, + // it is judged whether it is wrong or unsuccessful, + // and the compensation process is interrupted. + if (Boolean.TRUE.equals(context.getVariable(DomainConstants.VAR_NAME_FIRST_COMPENSATION_STATE_STARTED))) { + + Exception exception = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + if (exception != null) { + EngineUtils.endStateMachine(context); + return null; + } + + StateInstance stateInstance = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + if (stateInstance != null && (!ExecutionStatus.SU.equals(stateInstance.getStatus()))) { + EngineUtils.endStateMachine(context); + return null; + } + } + + Stack stateStackToBeCompensated = CompensationHolder.getCurrent(context, true) + .getStateStackNeedCompensation(); + if (!stateStackToBeCompensated.isEmpty()) { + + StateInstance stateToBeCompensated = stateStackToBeCompensated.pop(); + + StateMachine stateMachine = (StateMachine)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE); + State state = stateMachine.getState(EngineUtils.getOriginStateName(stateToBeCompensated)); + if (state != null && state instanceof AbstractTaskState) { + + AbstractTaskState taskState = (AbstractTaskState)state; + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + + State compensateState = null; + String compensateStateName = taskState.getCompensateState(); + if (StringUtils.hasLength(compensateStateName)) { + compensateState = stateMachine.getState(compensateStateName); + } + + if (compensateState == null && (taskState instanceof SubStateMachine)) { + compensateState = ((SubStateMachine)taskState).getCompensateStateObject(); + instruction.setTemporaryState(compensateState); + } + + if (compensateState == null) { + EngineUtils.endStateMachine(context); + return null; + } + + instruction.setStateName(compensateState.getName()); + + CompensationHolder.getCurrent(context, true).addToBeCompensatedState(compensateState.getName(), + stateToBeCompensated); + + ((HierarchicalProcessContext)context).setVariableLocally( + DomainConstants.VAR_NAME_FIRST_COMPENSATION_STATE_STARTED, true); + + if (compensateState instanceof CompensateSubStateMachineState) { + ((HierarchicalProcessContext)context).setVariableLocally( + compensateState.getName() + DomainConstants.VAR_NAME_SUB_MACHINE_PARENT_ID, + EngineUtils.generateParentId(stateToBeCompensated)); + } + + return instruction; + } + } + + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE); + + String compensationTriggerStateNext = compensationTriggerState.getNext(); + if (StringUtils.isEmpty(compensationTriggerStateNext)) { + EngineUtils.endStateMachine(context); + return null; + } + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + instruction.setStateName(compensationTriggerStateNext); + return instruction; + } + +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/CompensationHolder.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/CompensationHolder.java new file mode 100644 index 0000000..177f41f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/CompensationHolder.java @@ -0,0 +1,163 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; + +/** + * CompensationHolder + * + * @author lorne.cl + */ +public class CompensationHolder { + + /** + * states need compensation + * key: stateName + */ + private Map statesNeedCompensation = new ConcurrentHashMap<>(); + + /** + * states used to compensation + * key: stateName + */ + private Map statesForCompensation = new ConcurrentHashMap<>(); + + /** + * stateStack need compensation + */ + private Stack stateStackNeedCompensation = new Stack<>(); + + public static CompensationHolder getCurrent(ProcessContext context, boolean forceCreate) { + + CompensationHolder compensationholder = (CompensationHolder)context.getVariable( + DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER); + if (compensationholder == null && forceCreate) { + synchronized (context) { + + compensationholder = (CompensationHolder)context.getVariable( + DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER); + if (compensationholder == null) { + compensationholder = new CompensationHolder(); + context.setVariable(DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER, compensationholder); + } + } + } + return compensationholder; + } + + public static List findStateInstListToBeCompensated(ProcessContext context, + List stateInstanceList) { + List stateListToBeCompensated = null; + if (CollectionUtils.isNotEmpty(stateInstanceList)) { + stateListToBeCompensated = new ArrayList<>(stateInstanceList.size()); + + StateMachine stateMachine = (StateMachine)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE); + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + for (StateInstance stateInstance : stateInstanceList) { + if (stateNeedToCompensate(stateInstance)) { + State state = stateMachine.getState(EngineUtils.getOriginStateName(stateInstance)); + AbstractTaskState taskState = null; + if (state instanceof AbstractTaskState) { + taskState = (AbstractTaskState)state; + } + + //The data update service is not configured with the compensation state, + // The state machine needs to exit directly without compensation. + if (stateInstance.isForUpdate() && taskState != null && StringUtils.isBlank( + taskState.getCompensateState())) { + + String message = "StateMachineInstance[" + stateMachineInstance.getId() + ":" + stateMachine + .getName() + "] have a state [" + stateInstance.getName() + + "] is a service for update data, but no compensateState found."; + EngineExecutionException exception = ExceptionUtils.createEngineExecutionException( + FrameworkErrorCode.CompensationStateNotFound, message, stateMachineInstance, stateInstance); + + EngineUtils.failStateMachine(context, exception); + + throw exception; + } + + if (taskState != null && StringUtils.isNotBlank(taskState.getCompensateState())) { + stateListToBeCompensated.add(stateInstance); + } + } + } + } + return stateListToBeCompensated; + } + + private static boolean stateNeedToCompensate(StateInstance stateInstance) { + //If it has been retried, it will not be compensated + if (stateInstance.isIgnoreStatus()) { + return false; + } + if (DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(stateInstance.getType())) { + + return (!ExecutionStatus.FA.equals(stateInstance.getStatus())) && (!ExecutionStatus.SU.equals( + stateInstance.getCompensationStatus())); + } else { + + return DomainConstants.STATE_TYPE_SERVICE_TASK.equals(stateInstance.getType()) && !stateInstance + .isForCompensation() && (!ExecutionStatus.FA.equals(stateInstance.getStatus())) && (!ExecutionStatus.SU + .equals(stateInstance.getCompensationStatus())); + } + } + + public static void clearCurrent(ProcessContext context) { + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_COMPENSATION_HOLDER); + } + + public Map getStatesNeedCompensation() { + return statesNeedCompensation; + } + + public void addToBeCompensatedState(String stateName, StateInstance toBeCompensatedState) { + this.statesNeedCompensation.put(stateName, toBeCompensatedState); + } + + public Map getStatesForCompensation() { + return statesForCompensation; + } + + public void addForCompensationState(String stateName, StateInstance forCompensationState) { + this.statesForCompensation.put(stateName, forCompensationState); + } + + public Stack getStateStackNeedCompensation() { + return stateStackNeedCompensation; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/EngineUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/EngineUtils.java new file mode 100644 index 0000000..e29f5f8 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/EngineUtils.java @@ -0,0 +1,251 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.utils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.AsyncCallback; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.engine.pcext.handlers.ScriptTaskStateHandler; +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.TaskState; +import io.seata.saga.statelang.domain.TaskState.ExceptionMatch; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author lorne.cl + */ +public class EngineUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(EngineUtils.class); + + /** + * generate parent id + * + * @param stateInstance + * @return + */ + public static String generateParentId(StateInstance stateInstance) { + return stateInstance.getMachineInstanceId() + DomainConstants.SEPERATOR_PARENT_ID + stateInstance.getId(); + } + + /** + * get origin state name without suffix like fork + * + * @param stateInstance + * @return + * @see LoopTaskUtils#generateLoopStateName(ProcessContext, String) + */ + public static String getOriginStateName(StateInstance stateInstance) { + String stateName = stateInstance.getName(); + if (StringUtils.isNotBlank(stateName)) { + int end = stateName.lastIndexOf(LoopTaskUtils.LOOP_STATE_NAME_PATTERN); + if (end > -1) { + return stateName.substring(0, end); + } + } + return stateName; + } + + /** + * end StateMachine + * + * @param context + */ + public static void endStateMachine(ProcessContext context) { + + if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) { + if (context.hasVariable(DomainConstants.LOOP_SEMAPHORE)) { + Semaphore semaphore = (Semaphore)context.getVariable(DomainConstants.LOOP_SEMAPHORE); + semaphore.release(); + } + return; + } + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + stateMachineInstance.setGmtEnd(new Date()); + + Exception exp = (Exception)context.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION); + if (exp != null) { + stateMachineInstance.setException(exp); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exception Occurred: " + exp); + } + } + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + stateMachineConfig.getStatusDecisionStrategy().decideOnEndState(context, stateMachineInstance, exp); + + stateMachineInstance.getEndParams().putAll( + (Map)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT)); + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + instruction.setEnd(true); + + stateMachineInstance.setRunning(false); + stateMachineInstance.setGmtEnd(new Date()); + + if (stateMachineInstance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) { + stateMachineConfig.getStateLogStore().recordStateMachineFinished(stateMachineInstance, context); + } + + AsyncCallback callback = (AsyncCallback)context.getVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK); + if (callback != null) { + if (exp != null) { + callback.onError(context, stateMachineInstance, exp); + } else { + callback.onFinished(context, stateMachineInstance); + } + } + } + + /** + * fail StateMachine + * + * @param context + * @param exp + */ + public static void failStateMachine(ProcessContext context, Exception exp) { + + if (context.hasVariable(DomainConstants.VAR_NAME_IS_LOOP_STATE)) { + return; + } + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + stateMachineConfig.getStatusDecisionStrategy().decideOnTaskStateFail(context, stateMachineInstance, exp); + + stateMachineInstance.getEndParams().putAll( + (Map)context.getVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT)); + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + instruction.setEnd(true); + + stateMachineInstance.setRunning(false); + stateMachineInstance.setGmtEnd(new Date()); + stateMachineInstance.setException(exp); + + if (stateMachineInstance.getStateMachine().isPersist() && stateMachineConfig.getStateLogStore() != null) { + stateMachineConfig.getStateLogStore().recordStateMachineFinished(stateMachineInstance, context); + } + + AsyncCallback callback = (AsyncCallback)context.getVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK); + if (callback != null) { + callback.onError(context, stateMachineInstance, exp); + } + } + + /** + * test if is timeout + * @param gmtUpdated + * @param timeoutMillis + * @return + */ + public static boolean isTimeout(Date gmtUpdated, int timeoutMillis) { + if (gmtUpdated == null || timeoutMillis < 0) { + return false; + } + return System.currentTimeMillis() - gmtUpdated.getTime() > timeoutMillis; + } + + /** + * Handle exceptions while ServiceTask or ScriptTask Executing + * + * @param context + * @param state + * @param e + */ + public static void handleException(ProcessContext context, AbstractTaskState state, Throwable e) { + List catches = state.getCatches(); + if (CollectionUtils.isNotEmpty(catches)) { + for (TaskState.ExceptionMatch exceptionMatch : catches) { + + List exceptions = exceptionMatch.getExceptions(); + List> exceptionClasses = exceptionMatch.getExceptionClasses(); + if (CollectionUtils.isNotEmpty(exceptions)) { + if (exceptionClasses == null) { + synchronized (exceptionMatch) { + exceptionClasses = exceptionMatch.getExceptionClasses(); + if (exceptionClasses == null) { + + exceptionClasses = new ArrayList<>(exceptions.size()); + for (String expStr : exceptions) { + + Class expClass = null; + try { + expClass = (Class) ScriptTaskStateHandler.class + .getClassLoader().loadClass(expStr); + } catch (Exception e1) { + + LOGGER.warn("Cannot Load Exception Class by getClass().getClassLoader()", e1); + + try { + expClass = (Class) Thread.currentThread() + .getContextClassLoader().loadClass(expStr); + } catch (Exception e2) { + LOGGER.warn( + "Cannot Load Exception Class by Thread.currentThread()" + + ".getContextClassLoader()", + e2); + } + } + + if (expClass != null) { + exceptionClasses.add(expClass); + } + } + exceptionMatch.setExceptionClasses(exceptionClasses); + } + } + } + + for (Class expClass : exceptionClasses) { + if (expClass.isAssignableFrom(e.getClass())) { + ((HierarchicalProcessContext) context).setVariableLocally( + DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE, exceptionMatch.getNext()); + return; + } + } + + } + } + } + + LOGGER.error("Task execution failed and no catches configured"); + ((HierarchicalProcessContext) context).setVariableLocally(DomainConstants.VAR_NAME_IS_EXCEPTION_NOT_CATCH, true); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopContextHolder.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopContextHolder.java new file mode 100644 index 0000000..fdd69f4 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopContextHolder.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.utils; + +import java.util.Collection; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicInteger; + +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; + +/** + * Loop Context Holder for Loop Attributes + * + * @author anselleeyy + */ +public class LoopContextHolder { + + private final AtomicInteger nrOfInstances = new AtomicInteger(); + private final AtomicInteger nrOfActiveInstances = new AtomicInteger(); + private final AtomicInteger nrOfCompletedInstances = new AtomicInteger(); + private volatile boolean failEnd = false; + private volatile boolean completionConditionSatisfied = false; + private final Stack loopCounterStack = new Stack<>(); + private final Stack forwardCounterStack = new Stack<>(); + private Collection collection; + + public static LoopContextHolder getCurrent(ProcessContext context, boolean forceCreate) { + LoopContextHolder loopContextHolder = (LoopContextHolder)context.getVariable( + DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER); + + if (null == loopContextHolder && forceCreate) { + synchronized (context) { + loopContextHolder = (LoopContextHolder)context.getVariable( + DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER); + if (null == loopContextHolder) { + loopContextHolder = new LoopContextHolder(); + context.setVariable(DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER, loopContextHolder); + } + } + } + return loopContextHolder; + } + + public static void clearCurrent(ProcessContext context) { + context.removeVariable(DomainConstants.VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER); + } + + public AtomicInteger getNrOfInstances() { + return nrOfInstances; + } + + public AtomicInteger getNrOfActiveInstances() { + return nrOfActiveInstances; + } + + public AtomicInteger getNrOfCompletedInstances() { + return nrOfCompletedInstances; + } + + public boolean isFailEnd() { + return failEnd; + } + + public void setFailEnd(boolean failEnd) { + this.failEnd = failEnd; + } + + public boolean isCompletionConditionSatisfied() { + return completionConditionSatisfied; + } + + public void setCompletionConditionSatisfied(boolean completionConditionSatisfied) { + this.completionConditionSatisfied = completionConditionSatisfied; + } + + public Stack getLoopCounterStack() { + return loopCounterStack; + } + + public Stack getForwardCounterStack() { + return forwardCounterStack; + } + + public Collection getCollection() { + return collection; + } + + public void setCollection(Collection collection) { + this.collection = collection; + } +} diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java new file mode 100644 index 0000000..152cb3f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/LoopTaskUtils.java @@ -0,0 +1,429 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EmptyStackException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.NumberUtils; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.evaluation.EvaluatorFactoryManager; +import io.seata.saga.engine.evaluation.expression.ExpressionEvaluator; +import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.engine.pcext.StateInstruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.impl.ProcessContextImpl; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.domain.TaskState.Loop; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Loop Task Util + * + * @author anselleeyy + */ +public class LoopTaskUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(LoopTaskUtils.class); + + private static final String DEFAULT_COMPLETION_CONDITION = "[nrOfInstances] == [nrOfCompletedInstances]"; + public static final String LOOP_STATE_NAME_PATTERN = "-loop-"; + + private static final Map EXPRESSION_EVALUATOR_MAP = new ConcurrentHashMap<>(); + + /** + * get Loop Config from State + * + * @param context + * @param currentState + * @return currentState loop config if satisfied, else {@literal null} + */ + public static Loop getLoopConfig(ProcessContext context, State currentState) { + if (matchLoop(currentState)) { + AbstractTaskState taskState = (AbstractTaskState)currentState; + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + if (null != taskState.getLoop()) { + Loop loop = taskState.getLoop(); + String collectionName = loop.getCollection(); + if (StringUtils.isNotBlank(collectionName)) { + Object expression = ParameterUtils.createValueExpression( + stateMachineConfig.getExpressionFactoryManager(), collectionName); + Object collection = ParameterUtils.getValue(expression, stateMachineInstance.getContext(), null); + if (collection instanceof Collection && ((Collection)collection).size() > 0) { + LoopContextHolder.getCurrent(context, true).setCollection((Collection)collection); + return loop; + } + } + LOGGER.warn("State [{}] loop collection param [{}] invalid", currentState.getName(), collectionName); + } + } + return null; + } + + /** + * match if state has loop property + * + * @param state + * @return + */ + public static boolean matchLoop(State state) { + return state != null && (DomainConstants.STATE_TYPE_SERVICE_TASK.equals(state.getType()) + || DomainConstants.STATE_TYPE_SCRIPT_TASK.equals(state.getType()) + || DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals(state.getType())); + } + + /** + * create loop counter context + * + * @param context + */ + public static void createLoopCounterContext(ProcessContext context) { + LoopContextHolder contextHolder = LoopContextHolder.getCurrent(context, true); + Collection collection = contextHolder.getCollection(); + contextHolder.getNrOfInstances().set(collection.size()); + + for (int i = collection.size() - 1; i >= 0; i--) { + contextHolder.getLoopCounterStack().push(i); + } + } + + + /** + * reload loop counter context while forward + * + * @param context + * @param forwardStateName + */ + public static void reloadLoopContext(ProcessContext context, String forwardStateName) { + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + + List actList = stateMachineInstance.getStateList(); + List forwardStateList = actList.stream().filter( + e -> forwardStateName.equals(EngineUtils.getOriginStateName(e))).collect(Collectors.toList()); + + LoopContextHolder loopContextHolder = LoopContextHolder.getCurrent(context, true); + Collection collection = loopContextHolder.getCollection(); + + LinkedList list = new LinkedList<>(); + for (int i = 0; i < collection.size(); i++) { + list.addFirst(i); + } + int executedNumber = 0; + LinkedList failEndList = new LinkedList<>(); + for (StateInstance stateInstance : forwardStateList) { + if (!stateInstance.isIgnoreStatus()) { + if (ExecutionStatus.SU.equals(stateInstance.getStatus())) { + executedNumber += 1; + } else { + stateInstance.setIgnoreStatus(true); + failEndList.addFirst(reloadLoopCounter(stateInstance.getName())); + } + list.remove(Integer.valueOf(reloadLoopCounter(stateInstance.getName()))); + } + } + + loopContextHolder.getLoopCounterStack().addAll(list); + loopContextHolder.getForwardCounterStack().addAll(failEndList); + loopContextHolder.getNrOfInstances().set(collection.size()); + loopContextHolder.getNrOfCompletedInstances().set(executedNumber); + } + + /** + * create context for async publish + * + * @param context + * @param loopCounter acquire new counter if is -1, else means a specific loop-counter + * @return + */ + public static ProcessContext createLoopEventContext(ProcessContext context, int loopCounter) { + ProcessContextImpl copyContext = new ProcessContextImpl(); + copyContext.setParent(context); + copyContext.setVariableLocally(DomainConstants.LOOP_COUNTER, loopCounter >= 0 ? loopCounter : acquireNextLoopCounter(context)); + copyContext.setInstruction(copyInstruction(context.getInstruction(StateInstruction.class))); + return copyContext; + } + + public static StateInstance findOutLastNeedForwardStateInstance(ProcessContext context) { + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateInstance lastForwardState = (StateInstance)context.getVariable(DomainConstants.VAR_NAME_STATE_INST); + + List actList = stateMachineInstance.getStateList(); + for (int i = actList.size() - 1; i >= 0; i--) { + StateInstance stateInstance = actList.get(i); + if (EngineUtils.getOriginStateName(stateInstance).equals(EngineUtils.getOriginStateName(lastForwardState)) + && !ExecutionStatus.SU.equals(stateInstance.getStatus())) { + return stateInstance; + } + } + return lastForwardState; + } + + public static StateInstance findOutLastRetriedStateInstance(StateMachineInstance stateMachineInstance, + String stateName) { + List actList = stateMachineInstance.getStateList(); + for (int i = actList.size() - 1; i >= 0; i--) { + StateInstance stateInstance = actList.get(i); + if (stateInstance.getName().equals(stateName)) { + return stateInstance; + } + } + return null; + } + + /** + * check if satisfied completion condition + * + * @param context + * @return + */ + public static boolean isCompletionConditionSatisfied(ProcessContext context) { + + StateInstruction instruction = context.getInstruction(StateInstruction.class); + AbstractTaskState currentState = (AbstractTaskState)instruction.getState(context); + LoopContextHolder currentLoopContext = LoopContextHolder.getCurrent(context, true); + + if (currentLoopContext.isCompletionConditionSatisfied()) { + return true; + } + + int nrOfInstances = currentLoopContext.getNrOfInstances().get(); + int nrOfActiveInstances = currentLoopContext.getNrOfActiveInstances().get(); + int nrOfCompletedInstances = currentLoopContext.getNrOfCompletedInstances().get(); + + if (!currentLoopContext.isCompletionConditionSatisfied()) { + synchronized (currentLoopContext) { + if (!currentLoopContext.isCompletionConditionSatisfied()) { + Map stateMachineContext = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + // multi-instance variables should be double/float while evaluate + stateMachineContext.put(DomainConstants.NUMBER_OF_INSTANCES, (double)nrOfInstances); + stateMachineContext.put(DomainConstants.NUMBER_OF_ACTIVE_INSTANCES, (double)nrOfActiveInstances); + stateMachineContext.put(DomainConstants.NUMBER_OF_COMPLETED_INSTANCES, + (double)nrOfCompletedInstances); + + if (nrOfCompletedInstances >= nrOfInstances || getEvaluator(context, + currentState.getLoop().getCompletionCondition()).evaluate(stateMachineContext)) { + currentLoopContext.setCompletionConditionSatisfied(true); + } + } + } + } + + return currentLoopContext.isCompletionConditionSatisfied(); + } + + public static int acquireNextLoopCounter(ProcessContext context) { + try { + return LoopContextHolder.getCurrent(context, true).getLoopCounterStack().pop(); + } catch (EmptyStackException e) { + return -1; + } + } + + /** + * generate loop state name like stateName-fork-1 + * + * @param stateName + * @param context + * @return + */ + public static String generateLoopStateName(ProcessContext context, String stateName) { + if (StringUtils.isNotBlank(stateName)) { + int loopCounter = (int)context.getVariable(DomainConstants.LOOP_COUNTER); + return stateName + LOOP_STATE_NAME_PATTERN + loopCounter; + } + return stateName; + } + + /** + * reload context loop counter from stateInstName + * + * @param stateName + * @return + * @see #generateLoopStateName(ProcessContext, String) + */ + public static int reloadLoopCounter(String stateName) { + if (StringUtils.isNotBlank(stateName)) { + int end = stateName.lastIndexOf(LOOP_STATE_NAME_PATTERN); + if (end > -1) { + String loopCounter = stateName.substring(end + LOOP_STATE_NAME_PATTERN.length()); + return NumberUtils.toInt(loopCounter, -1); + } + } + return -1; + } + + /** + * put loop out params to parent context + * + * @param context + */ + public static void putContextToParent(ProcessContext context, List subContextList, State state) { + + Map contextVariables = (Map)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT); + if (CollectionUtils.isNotEmpty(subContextList)) { + + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + List> subContextVariables = new ArrayList<>(); + for (ProcessContext subProcessContext : subContextList) { + StateInstance stateInstance = (StateInstance)subProcessContext.getVariable(DomainConstants.VAR_NAME_STATE_INST); + + Map outputVariablesToContext = ParameterUtils.createOutputParams( + stateMachineConfig.getExpressionFactoryManager(), (AbstractTaskState)state, stateInstance.getOutputParams()); + subContextVariables.add(outputVariablesToContext); + } + + contextVariables.put(DomainConstants.LOOP_RESULT, subContextVariables); + } + + } + + /** + * forward with subStateMachine should check each loop state's status + * + * @param context + * @return + */ + public static boolean isForSubStateMachineForward(ProcessContext context) { + + StateMachineInstance stateMachineInstance = (StateMachineInstance)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_INST); + StateInstruction instruction = context.getInstruction(StateInstruction.class); + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + + StateInstance lastRetriedStateInstance = LoopTaskUtils.findOutLastRetriedStateInstance( + stateMachineInstance, LoopTaskUtils.generateLoopStateName(context, instruction.getStateName())); + + if (null != lastRetriedStateInstance && DomainConstants.STATE_TYPE_SUB_STATE_MACHINE.equals( + lastRetriedStateInstance.getType()) && !ExecutionStatus.SU.equals( + lastRetriedStateInstance.getCompensationStatus())) { + + while (StringUtils.isNotBlank(lastRetriedStateInstance.getStateIdRetriedFor())) { + lastRetriedStateInstance = stateMachineConfig.getStateLogStore().getStateInstance( + lastRetriedStateInstance.getStateIdRetriedFor(), lastRetriedStateInstance.getMachineInstanceId()); + } + + List subInst = stateMachineConfig.getStateLogStore() + .queryStateMachineInstanceByParentId(EngineUtils.generateParentId(lastRetriedStateInstance)); + if (CollectionUtils.isNotEmpty(subInst)) { + if (ExecutionStatus.SU.equals(subInst.get(0).getCompensationStatus())) { + return false; + } + } + + if (ExecutionStatus.UN.equals(subInst.get(0).getCompensationStatus())) { + throw new ForwardInvalidException( + "Last forward execution state instance is SubStateMachine and compensation status is " + + "[UN], Operation[forward] denied, stateInstanceId:" + + lastRetriedStateInstance.getId(), FrameworkErrorCode.OperationDenied); + } + + return true; + } + return false; + } + + /** + * decide current exception route for loop publish over + * + * @param subContextList + * @param stateMachine + * @return route if current exception route not null + */ + public static String decideCurrentExceptionRoute(List subContextList, StateMachine stateMachine) { + + String route = null; + if (CollectionUtils.isNotEmpty(subContextList)) { + + for (ProcessContext processContext : subContextList) { + String next = (String)processContext.getVariable(DomainConstants.VAR_NAME_CURRENT_EXCEPTION_ROUTE); + if (StringUtils.isNotBlank(next)) { + + // compensate must be execute + State state = stateMachine.getState(next); + if (DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER.equals(state.getType())) { + route = next; + break; + } else if (null == route) { + route = next; + } + } + } + } + return route; + } + + /** + * get loop completion condition evaluator + * + * @param context + * @param completionCondition + * @return + */ + private static ExpressionEvaluator getEvaluator(ProcessContext context, String completionCondition) { + if (StringUtils.isBlank(completionCondition)) { + completionCondition = DEFAULT_COMPLETION_CONDITION; + } + if (!EXPRESSION_EVALUATOR_MAP.containsKey(completionCondition)) { + StateMachineConfig stateMachineConfig = (StateMachineConfig)context.getVariable( + DomainConstants.VAR_NAME_STATEMACHINE_CONFIG); + ExpressionEvaluator expressionEvaluator = (ExpressionEvaluator)stateMachineConfig + .getEvaluatorFactoryManager().getEvaluatorFactory(EvaluatorFactoryManager.EVALUATOR_TYPE_DEFAULT) + .createEvaluator(completionCondition); + expressionEvaluator.setRootObjectName(null); + EXPRESSION_EVALUATOR_MAP.put(completionCondition, expressionEvaluator); + } + return EXPRESSION_EVALUATOR_MAP.get(completionCondition); + } + + private static StateInstruction copyInstruction(StateInstruction instruction) { + StateInstruction targetInstruction = new StateInstruction(); + targetInstruction.setStateMachineName(instruction.getStateMachineName()); + targetInstruction.setTenantId(instruction.getTenantId()); + targetInstruction.setStateName(instruction.getStateName()); + targetInstruction.setTemporaryState(instruction.getTemporaryState()); + return targetInstruction; + } + +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java new file mode 100644 index 0000000..a44432b --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/pcext/utils/ParameterUtils.java @@ -0,0 +1,177 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.pcext.utils; + +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.expression.Expression; +import io.seata.saga.engine.expression.ExpressionFactory; +import io.seata.saga.engine.expression.ExpressionFactoryManager; +import io.seata.saga.engine.expression.seq.SequenceExpression; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import io.seata.saga.statelang.domain.impl.StateInstanceImpl; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * + * ParameterUtils + * + * @author lorne.cl + */ +public class ParameterUtils { + + public static List createInputParams(ExpressionFactoryManager expressionFactoryManager, + StateInstanceImpl stateInstance, + AbstractTaskState serviceTaskState, Object variablesFrom) { + List inputAssignments = serviceTaskState.getInput(); + if (CollectionUtils.isEmpty(inputAssignments)) { + return new ArrayList<>(0); + } + + List inputExpressions = serviceTaskState.getInputExpressions(); + if (inputExpressions == null) { + synchronized (serviceTaskState) { + inputExpressions = serviceTaskState.getInputExpressions(); + if (inputExpressions == null) { + inputExpressions = new ArrayList<>(inputAssignments.size()); + for (Object inputAssignment : inputAssignments) { + inputExpressions.add(createValueExpression(expressionFactoryManager, inputAssignment)); + } + } + serviceTaskState.setInputExpressions(inputExpressions); + } + } + List inputValues = new ArrayList<>(inputExpressions.size()); + for (Object valueExpression : inputExpressions) { + Object value = getValue(valueExpression, variablesFrom, stateInstance); + inputValues.add(value); + } + + return inputValues; + } + + public static Map createOutputParams(ExpressionFactoryManager expressionFactoryManager, + AbstractTaskState serviceTaskState, Object variablesFrom) { + Map outputAssignments = serviceTaskState.getOutput(); + if (CollectionUtils.isEmpty(outputAssignments)) { + return new LinkedHashMap<>(0); + } + + Map outputExpressions = serviceTaskState.getOutputExpressions(); + if (outputExpressions == null) { + synchronized (serviceTaskState) { + outputExpressions = serviceTaskState.getOutputExpressions(); + if (outputExpressions == null) { + outputExpressions = new LinkedHashMap<>(outputAssignments.size()); + for (Map.Entry entry : outputAssignments.entrySet()) { + outputExpressions.put(entry.getKey(), + createValueExpression(expressionFactoryManager, entry.getValue())); + } + } + serviceTaskState.setOutputExpressions(outputExpressions); + } + } + Map outputValues = new LinkedHashMap<>(outputExpressions.size()); + for (String paramName : outputExpressions.keySet()) { + outputValues.put(paramName, getValue(outputExpressions.get(paramName), variablesFrom, null)); + } + return outputValues; + } + + public static Object getValue(Object valueExpression, Object variablesFrom, StateInstance stateInstance) { + if (valueExpression instanceof Expression) { + Object value = ((Expression)valueExpression).getValue(variablesFrom); + if (value != null && stateInstance != null && StringUtils.isEmpty(stateInstance.getBusinessKey()) + && valueExpression instanceof SequenceExpression) { + stateInstance.setBusinessKey(String.valueOf(value)); + } + return value; + } else if (valueExpression instanceof Map) { + Map mapValueExpression = (Map)valueExpression; + Map mapValue = new LinkedHashMap<>(); + mapValueExpression.forEach((key, value) -> { + value = getValue(value, variablesFrom, stateInstance); + if (value != null) { + mapValue.put(key, value); + } + }); + return mapValue; + } else if (valueExpression instanceof List) { + List listValueExpression = (List)valueExpression; + List listValue = new ArrayList<>(listValueExpression.size()); + for (Object aValueExpression : listValueExpression) { + listValue.add(getValue(aValueExpression, variablesFrom, stateInstance)); + } + return listValue; + } else { + return valueExpression; + } + } + + public static Object createValueExpression(ExpressionFactoryManager expressionFactoryManager, + Object paramAssignment) { + + Object valueExpression; + + if (paramAssignment instanceof Expression) { + valueExpression = paramAssignment; + } else if (paramAssignment instanceof Map) { + Map paramMapAssignment = (Map)paramAssignment; + Map paramMap = new LinkedHashMap<>(paramMapAssignment.size()); + paramMapAssignment.forEach((paramName, valueAssignment) -> { + paramMap.put(paramName, createValueExpression(expressionFactoryManager, valueAssignment)); + }); + valueExpression = paramMap; + } else if (paramAssignment instanceof List) { + List paramListAssignment = (List)paramAssignment; + List paramList = new ArrayList<>(paramListAssignment.size()); + for (Object aParamAssignment : paramListAssignment) { + paramList.add(createValueExpression(expressionFactoryManager, aParamAssignment)); + } + valueExpression = paramList; + } else if (paramAssignment instanceof String && ((String)paramAssignment).startsWith("$")) { + + String expressionStr = (String)paramAssignment; + int expTypeStart = expressionStr.indexOf("$"); + int expTypeEnd = expressionStr.indexOf(".", expTypeStart); + + String expressionType = null; + if (expTypeStart >= 0 && expTypeEnd > expTypeStart) { + expressionType = expressionStr.substring(expTypeStart + 1, expTypeEnd); + } + + int expEnd = expressionStr.length(); + String expressionContent = null; + if (expTypeEnd > 0 && expEnd > expTypeEnd) { + expressionContent = expressionStr.substring(expTypeEnd + 1, expEnd); + } + + ExpressionFactory expressionFactory = expressionFactoryManager.getExpressionFactory(expressionType); + if (expressionFactory == null) { + throw new IllegalArgumentException("Cannot get ExpressionFactory by Type[" + expressionType + "]"); + } + valueExpression = expressionFactory.createExpression(expressionContent); + } else { + valueExpression = paramAssignment; + } + return valueExpression; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateLogRepository.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateLogRepository.java new file mode 100644 index 0000000..0a3b171 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateLogRepository.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.repo; + +import java.util.List; + +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * State Log Repository + * + * @author lorne.cl + */ +public interface StateLogRepository { + + /** + * Get state machine instance + * + * @param stateMachineInstanceId + * @return + */ + StateMachineInstance getStateMachineInstance(String stateMachineInstanceId); + + /** + * Get state machine instance by businessKey + * + * @param businessKey + * @param tenantId + * @return + */ + StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId); + + /** + * Query the list of state machine instances by parent id + * + * @param parentId + * @return + */ + List queryStateMachineInstanceByParentId(String parentId); + + /** + * Get state instance + * + * @param stateInstanceId + * @param machineInstId + * @return + */ + StateInstance getStateInstance(String stateInstanceId, String machineInstId); + + /** + * Get a list of state instances by state machine instance id + * + * @param stateMachineInstanceId + * @return + */ + List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java new file mode 100644 index 0000000..e865152 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/StateMachineRepository.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.repo; + +import java.io.IOException; + +import io.seata.saga.statelang.domain.StateMachine; +import org.springframework.core.io.Resource; + +/** + * StateMachineRepository + * + * @author lorne.cl + */ +public interface StateMachineRepository { + + /** + * Gets get state machine by id. + * + * @param stateMachineId the state machine id + * @return the get state machine by id + */ + StateMachine getStateMachineById(String stateMachineId); + + /** + * Gets get state machine. + * + * @param stateMachineName the state machine name + * @param tenantId the tenant id + * @return the get state machine + */ + StateMachine getStateMachine(String stateMachineName, String tenantId); + + /** + * Gets get state machine. + * + * @param stateMachineName the state machine name + * @param tenantId the tenant id + * @param version the version + * @return the get state machine + */ + StateMachine getStateMachine(String stateMachineName, String tenantId, String version); + + /** + * Register the state machine to the repository (if the same version already exists, return the existing version) + * + * @param stateMachine + */ + StateMachine registryStateMachine(StateMachine stateMachine); + + /** + * registry by resources + * + * @param resources + * @param tenantId + */ + void registryByResources(Resource[] resources, String tenantId) throws IOException; +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateLogRepositoryImpl.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateLogRepositoryImpl.java new file mode 100644 index 0000000..a036e33 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateLogRepositoryImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.repo.impl; + +import java.util.List; + +import io.seata.saga.engine.repo.StateLogRepository; +import io.seata.saga.engine.store.StateLogStore; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * State Log Repository + * + * @author lorne.cl + */ +public class StateLogRepositoryImpl implements StateLogRepository { + + private StateLogStore stateLogStore; + + @Override + public StateMachineInstance getStateMachineInstance(String stateMachineInstanceId) { + if (stateLogStore == null) { + return null; + } + return stateLogStore.getStateMachineInstance(stateMachineInstanceId); + } + + @Override + public StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId) { + if (stateLogStore == null) { + return null; + } + return stateLogStore.getStateMachineInstanceByBusinessKey(businessKey, tenantId); + } + + @Override + public List queryStateMachineInstanceByParentId(String parentId) { + if (stateLogStore == null) { + return null; + } + return stateLogStore.queryStateMachineInstanceByParentId(parentId); + } + + @Override + public StateInstance getStateInstance(String stateInstanceId, String machineInstId) { + if (stateLogStore == null) { + return null; + } + return stateLogStore.getStateInstance(stateInstanceId, machineInstId); + } + + @Override + public List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId) { + if (stateLogStore == null) { + return null; + } + return stateLogStore.queryStateInstanceListByMachineInstanceId(stateMachineInstanceId); + } + + public void setStateLogStore(StateLogStore stateLogStore) { + this.stateLogStore = stateLogStore; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateMachineRepositoryImpl.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateMachineRepositoryImpl.java new file mode 100644 index 0000000..ef77b47 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/repo/impl/StateMachineRepositoryImpl.java @@ -0,0 +1,236 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.repo.impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.util.CollectionUtils; +import io.seata.common.util.StringUtils; +import io.seata.saga.engine.repo.StateMachineRepository; +import io.seata.saga.engine.sequence.SeqGenerator; +import io.seata.saga.engine.sequence.SpringJvmUUIDSeqGenerator; +import io.seata.saga.engine.store.StateLangStore; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.parser.StateMachineParserFactory; +import io.seata.saga.statelang.parser.utils.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.Resource; + +/** + * StateMachineRepository Implementation + * + * @author lorne.cl + */ +public class StateMachineRepositoryImpl implements StateMachineRepository { + + private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineRepositoryImpl.class); + private Map stateMachineMapByNameAndTenant = new ConcurrentHashMap<>(); + private Map stateMachineMapById = new ConcurrentHashMap<>(); + private StateLangStore stateLangStore; + private SeqGenerator seqGenerator = new SpringJvmUUIDSeqGenerator(); + private String charset = "UTF-8"; + private String defaultTenantId; + private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER; + + @Override + public StateMachine getStateMachineById(String stateMachineId) { + Item item = CollectionUtils.computeIfAbsent(stateMachineMapById, stateMachineId, + key -> new Item()); + if (item.getValue() == null && stateLangStore != null) { + synchronized (item) { + if (item.getValue() == null && stateLangStore != null) { + StateMachine stateMachine = stateLangStore.getStateMachineById(stateMachineId); + if (stateMachine != null) { + StateMachine parsedStatMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse( + stateMachine.getContent()); + if (parsedStatMachine == null) { + throw new RuntimeException( + "Parse State Language failed, stateMachineId:" + stateMachine.getId() + ", name:" + + stateMachine.getName()); + } + stateMachine.setStartState(parsedStatMachine.getStartState()); + stateMachine.getStates().putAll(parsedStatMachine.getStates()); + item.setValue(stateMachine); + stateMachineMapById.put(stateMachine.getName() + "_" + stateMachine.getTenantId(), + item); + } + } + } + } + return item.getValue(); + } + + @Override + public StateMachine getStateMachine(String stateMachineName, String tenantId) { + Item item = CollectionUtils.computeIfAbsent(stateMachineMapByNameAndTenant, stateMachineName + "_" + tenantId, + key -> new Item()); + if (item.getValue() == null && stateLangStore != null) { + synchronized (item) { + if (item.getValue() == null && stateLangStore != null) { + StateMachine stateMachine = stateLangStore.getLastVersionStateMachine(stateMachineName, tenantId); + if (stateMachine != null) { + StateMachine parsedStatMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse( + stateMachine.getContent()); + if (parsedStatMachine == null) { + throw new RuntimeException( + "Parse State Language failed, stateMachineId:" + stateMachine.getId() + ", name:" + + stateMachine.getName()); + } + stateMachine.setStartState(parsedStatMachine.getStartState()); + stateMachine.getStates().putAll(parsedStatMachine.getStates()); + item.setValue(stateMachine); + stateMachineMapById.put(stateMachine.getId(), item); + } + + } + } + } + return item.getValue(); + } + + @Override + public StateMachine getStateMachine(String stateMachineName, String tenantId, String version) { + throw new UnsupportedOperationException("not implement yet"); + } + + @Override + public StateMachine registryStateMachine(StateMachine stateMachine) { + + String stateMachineName = stateMachine.getName(); + String tenantId = stateMachine.getTenantId(); + + if (stateLangStore != null) { + StateMachine oldStateMachine = stateLangStore.getLastVersionStateMachine(stateMachineName, tenantId); + + if (oldStateMachine != null) { + byte[] oldBytesContent = null; + byte[] bytesContent = null; + try { + oldBytesContent = oldStateMachine.getContent().getBytes(charset); + bytesContent = stateMachine.getContent().getBytes(charset); + } catch (UnsupportedEncodingException e) { + LOGGER.error(e.getMessage(), e); + } + if (Arrays.equals(bytesContent, oldBytesContent) && stateMachine.getVersion() != null && stateMachine + .getVersion().equals(oldStateMachine.getVersion())) { + + LOGGER.info("StateMachine[{}] is already exist a same version", stateMachineName); + + stateMachine.setId(oldStateMachine.getId()); + stateMachine.setGmtCreate(oldStateMachine.getGmtCreate()); + + Item item = new Item(stateMachine); + stateMachineMapByNameAndTenant.put(stateMachineName + "_" + tenantId, item); + stateMachineMapById.put(stateMachine.getId(), item); + return stateMachine; + } + } + if (StringUtils.isBlank(stateMachine.getId())) { + stateMachine.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE)); + } + stateMachine.setGmtCreate(new Date()); + stateLangStore.storeStateMachine(stateMachine); + } + + if (StringUtils.isBlank(stateMachine.getId())) { + stateMachine.setId(seqGenerator.generate(DomainConstants.SEQ_ENTITY_STATE_MACHINE)); + } + + Item item = new Item(stateMachine); + stateMachineMapByNameAndTenant.put(stateMachineName + "_" + tenantId, item); + stateMachineMapById.put(stateMachine.getId(), item); + return stateMachine; + } + + @Override + public void registryByResources(Resource[] resources, String tenantId) throws IOException { + if (resources != null) { + for (Resource resource : resources) { + String json = IOUtils.toString(resource.getInputStream(), charset); + StateMachine stateMachine = StateMachineParserFactory.getStateMachineParser(jsonParserName).parse(json); + if (stateMachine != null) { + stateMachine.setContent(json); + if (StringUtils.isBlank(stateMachine.getTenantId())) { + stateMachine.setTenantId(tenantId); + } + registryStateMachine(stateMachine); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("===== StateMachine Loaded: \n{}", json); + } + } + } + } + } + + public void setStateLangStore(StateLangStore stateLangStore) { + this.stateLangStore = stateLangStore; + } + + public void setSeqGenerator(SeqGenerator seqGenerator) { + this.seqGenerator = seqGenerator; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public String getDefaultTenantId() { + return defaultTenantId; + } + + public void setDefaultTenantId(String defaultTenantId) { + this.defaultTenantId = defaultTenantId; + } + + public String getJsonParserName() { + return jsonParserName; + } + + public void setJsonParserName(String jsonParserName) { + this.jsonParserName = jsonParserName; + } + + private static class Item { + + private StateMachine value; + + private Item() { + } + + private Item(StateMachine value) { + this.value = value; + } + + public StateMachine getValue() { + return value; + } + + public void setValue(StateMachine value) { + this.value = value; + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SeqGenerator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SeqGenerator.java new file mode 100644 index 0000000..bf31298 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SeqGenerator.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.sequence; + +import java.util.List; + +/** + * SeqGenerator + * + * @author lorne.cl + */ +public interface SeqGenerator { + + /** + * Generate string. + * + * @param entity the entity + * @return the string + */ + String generate(String entity); + + /** + * Generate string. + * + * @param entity the entity + * @param shardingParameters the sharding parameters + * @return the string + */ + String generate(String entity, List shardingParameters); + + /** + * Generate string. + * + * @param entity the entity + * @param ruleName the rule name + * @param shardingParameters the sharding parameters + * @return the string + */ + String generate(String entity, String ruleName, List shardingParameters); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SpringJvmUUIDSeqGenerator.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SpringJvmUUIDSeqGenerator.java new file mode 100644 index 0000000..09b2a6f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/sequence/SpringJvmUUIDSeqGenerator.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.sequence; + +import java.util.List; + +import org.springframework.util.AlternativeJdkIdGenerator; +import org.springframework.util.IdGenerator; + +/** + * Based On Spring AlternativeJdkIdGenerator + * + * @author lorne.cl + */ +public class SpringJvmUUIDSeqGenerator implements SeqGenerator { + + private IdGenerator idGenerator = new AlternativeJdkIdGenerator(); + + @Override + public String generate(String entity, String ruleName, List shardingParameters) { + String uuid = idGenerator.generateId().toString(); + StringBuilder sb = new StringBuilder(uuid.length() - 4); + for (String seg : uuid.split("-")) { + sb.append(seg); + } + return sb.toString(); + } + + @Override + public String generate(String entity, List shardingParameters) { + return generate(entity, null, shardingParameters); + } + + @Override + public String generate(String entity) { + return generate(entity, null); + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLangStore.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLangStore.java new file mode 100644 index 0000000..9b730b3 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLangStore.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store; + +import io.seata.saga.statelang.domain.StateMachine; + +/** + * State language definition store + * + * @author lorne.cl + */ +public interface StateLangStore { + + /** + * Query the state machine definition by id + * + * @param stateMachineId + * @return + */ + StateMachine getStateMachineById(String stateMachineId); + + /** + * Get the latest version of the state machine by state machine name + * + * @param stateMachineName + * @param tenantId + * @return + */ + StateMachine getLastVersionStateMachine(String stateMachineName, String tenantId); + + /** + * Storage state machine definition + * + * @param stateMachine + * @return + */ + boolean storeStateMachine(StateMachine stateMachine); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLogStore.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLogStore.java new file mode 100644 index 0000000..ccef314 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/store/StateLogStore.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.store; + +import java.util.List; + +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * StateMachine engine log store + * + * @author lorne.cl + */ +public interface StateLogStore { + + /** + * Record state machine startup events + * + * @param machineInstance + */ + void recordStateMachineStarted(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record status end event + * + * @param machineInstance + */ + void recordStateMachineFinished(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record state machine restarted + * + * @param machineInstance + */ + void recordStateMachineRestarted(StateMachineInstance machineInstance, ProcessContext context); + + /** + * Record state start execution event + * + * @param stateInstance + */ + void recordStateStarted(StateInstance stateInstance, ProcessContext context); + + /** + * Record state execution end event + * + * @param stateInstance + */ + void recordStateFinished(StateInstance stateInstance, ProcessContext context); + + /** + * Get state machine instance + * + * @param stateMachineInstanceId + * @return + */ + StateMachineInstance getStateMachineInstance(String stateMachineInstanceId); + + /** + * Get state machine instance by businessKey + * + * @param businessKey + * @param tenantId + * @return + */ + StateMachineInstance getStateMachineInstanceByBusinessKey(String businessKey, String tenantId); + + /** + * Query the list of state machine instances by parent id + * + * @param parentId + * @return + */ + List queryStateMachineInstanceByParentId(String parentId); + + /** + * Get state instance + * + * @param stateInstanceId + * @param machineInstId + * @return + */ + StateInstance getStateInstance(String stateInstanceId, String machineInstId); + + /** + * Get a list of state instances by state machine instance id + * + * @param stateMachineInstanceId + * @return + */ + List queryStateInstanceListByMachineInstanceId(String stateMachineInstanceId); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/StatusDecisionStrategy.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/StatusDecisionStrategy.java new file mode 100644 index 0000000..4069e32 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/StatusDecisionStrategy.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.strategy; + +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * Default state machine execution status decision strategy. + * The strategy is to traverse the execution state of each state executed. + * If all state are successfully executed the state machine is successfully executed, + * if there is a state that fails to execute which is for data update, the state machine execution status is considered + * to be UN (the data is inconsistent), + * otherwise FA (failure: no data inconsistency) + * + * @author lorne.cl + */ +public interface StatusDecisionStrategy { + + /** + * Determine state machine execution status when executing to EndState + * + * @param context + * @param stateMachineInstance + * @param exp + */ + void decideOnEndState(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp); + + /** + * Determine state machine execution status when executing TaskState error + * + * @param context + * @param stateMachineInstance + * @param exp + */ + void decideOnTaskStateFail(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp); + + /** + * Determine the forward execution state of the state machine + * + * @param stateMachineInstance + * @param exp + * @param specialPolicy + * @return + */ + boolean decideMachineForwardExecutionStatus(StateMachineInstance stateMachineInstance, Exception exp, + boolean specialPolicy); +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/impl/DefaultStatusDecisionStrategy.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/impl/DefaultStatusDecisionStrategy.java new file mode 100644 index 0000000..4e4448f --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/strategy/impl/DefaultStatusDecisionStrategy.java @@ -0,0 +1,244 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.strategy.impl; + +import java.util.List; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.pcext.utils.CompensationHolder; +import io.seata.saga.engine.strategy.StatusDecisionStrategy; +import io.seata.saga.engine.utils.ExceptionUtils; +import io.seata.saga.engine.utils.ExceptionUtils.NetExceptionType; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default state machine execution status decision strategy + * + * @author lorne.cl + * @see StatusDecisionStrategy + */ +public class DefaultStatusDecisionStrategy implements StatusDecisionStrategy { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStatusDecisionStrategy.class); + + /** + * decide machine compensate status + * + * @param stateMachineInstance + * @param compensationHolder + */ + public static void decideMachineCompensateStatus(StateMachineInstance stateMachineInstance, + CompensationHolder compensationHolder) { + if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals(stateMachineInstance.getStatus())) { + + stateMachineInstance.setStatus(ExecutionStatus.UN); + } + if (!compensationHolder.getStateStackNeedCompensation().isEmpty()) { + + boolean hasCompensateSUorUN = false; + for (StateInstance forCompensateState : compensationHolder.getStatesForCompensation().values()) { + if (ExecutionStatus.UN.equals(forCompensateState.getStatus()) || ExecutionStatus.SU.equals( + forCompensateState.getStatus())) { + hasCompensateSUorUN = true; + break; + } + } + if (hasCompensateSUorUN) { + stateMachineInstance.setCompensationStatus(ExecutionStatus.UN); + } else { + stateMachineInstance.setCompensationStatus(ExecutionStatus.FA); + } + } else { + + boolean hasCompensateError = false; + for (StateInstance forCompensateState : compensationHolder.getStatesForCompensation().values()) { + if (!ExecutionStatus.SU.equals(forCompensateState.getStatus())) { + hasCompensateError = true; + break; + } + + } + if (hasCompensateError) { + stateMachineInstance.setCompensationStatus(ExecutionStatus.UN); + } else { + stateMachineInstance.setCompensationStatus(ExecutionStatus.SU); + } + } + } + + /** + * set machine status based on state list + * + * @param stateMachineInstance + * @param stateList + * @return + */ + public static void setMachineStatusBasedOnStateListAndException(StateMachineInstance stateMachineInstance, + List stateList, Exception exp) { + boolean hasSetStatus = false; + boolean hasSuccessUpdateService = false; + if (CollectionUtils.isNotEmpty(stateList)) { + boolean hasUnsuccessService = false; + + for (int i = stateList.size() - 1; i >= 0; i--) { + StateInstance stateInstance = stateList.get(i); + + if (stateInstance.isIgnoreStatus() || stateInstance.isForCompensation()) { + continue; + } + if (ExecutionStatus.UN.equals(stateInstance.getStatus())) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + hasSetStatus = true; + } else if (ExecutionStatus.SU.equals(stateInstance.getStatus())) { + if (DomainConstants.STATE_TYPE_SERVICE_TASK.equals(stateInstance.getType())) { + if (stateInstance.isForUpdate() && !stateInstance.isForCompensation()) { + hasSuccessUpdateService = true; + } + } + } else if (ExecutionStatus.SK.equals(stateInstance.getStatus())) { + // ignore + } else { + hasUnsuccessService = true; + } + } + + if (!hasSetStatus && hasUnsuccessService) { + if (hasSuccessUpdateService) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } else { + stateMachineInstance.setStatus(ExecutionStatus.FA); + } + hasSetStatus = true; + } + } + + if (!hasSetStatus) { + setMachineStatusBasedOnException(stateMachineInstance, exp, hasSuccessUpdateService); + } + } + + /** + * set machine status based on net exception + * + * @param stateMachineInstance + * @param exp + */ + public static void setMachineStatusBasedOnException(StateMachineInstance stateMachineInstance, Exception exp, + boolean hasSuccessUpdateService) { + if (exp == null) { + stateMachineInstance.setStatus(ExecutionStatus.SU); + } else if (exp instanceof EngineExecutionException + && FrameworkErrorCode.StateMachineExecutionTimeout.equals(((EngineExecutionException)exp).getErrcode())) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } else if (hasSuccessUpdateService) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } else { + NetExceptionType t = ExceptionUtils.getNetExceptionType(exp); + if (t != null) { + if (t.equals(NetExceptionType.CONNECT_EXCEPTION) || t.equals(NetExceptionType.CONNECT_TIMEOUT_EXCEPTION) + || t.equals(NetExceptionType.NOT_NET_EXCEPTION)) { + stateMachineInstance.setStatus(ExecutionStatus.FA); + } else if (t.equals(NetExceptionType.READ_TIMEOUT_EXCEPTION)) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } + } else { + stateMachineInstance.setStatus(ExecutionStatus.UN); + } + } + } + + @Override + public void decideOnEndState(ProcessContext context, StateMachineInstance stateMachineInstance, Exception exp) { + + if (ExecutionStatus.RU.equals(stateMachineInstance.getCompensationStatus())) { + + CompensationHolder compensationHolder = CompensationHolder.getCurrent(context, true); + decideMachineCompensateStatus(stateMachineInstance, compensationHolder); + } else { + Object failEndStateFlag = context.getVariable(DomainConstants.VAR_NAME_FAIL_END_STATE_FLAG); + boolean isComeFromFailEndState = failEndStateFlag != null && (Boolean)failEndStateFlag; + decideMachineForwardExecutionStatus(stateMachineInstance, exp, isComeFromFailEndState); + } + + if (stateMachineInstance.getCompensationStatus() != null && DomainConstants.OPERATION_NAME_FORWARD.equals( + context.getVariable(DomainConstants.VAR_NAME_OPERATION_NAME)) && ExecutionStatus.SU.equals( + stateMachineInstance.getStatus())) { + + stateMachineInstance.setCompensationStatus(ExecutionStatus.FA); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "StateMachine Instance[id:{},name:{}] execute finish with status[{}], compensation status [{}].", + stateMachineInstance.getId(), stateMachineInstance.getStateMachine().getName(), + stateMachineInstance.getStatus(), stateMachineInstance.getCompensationStatus()); + } + } + + @Override + public void decideOnTaskStateFail(ProcessContext context, StateMachineInstance stateMachineInstance, + Exception exp) { + if (!decideMachineForwardExecutionStatus(stateMachineInstance, exp, true)) { + + stateMachineInstance.setCompensationStatus(ExecutionStatus.UN); + } + } + + /** + * Determine the forward execution state of the state machine + * + * @param stateMachineInstance + * @param exp + * @param specialPolicy + * @return + */ + @Override + public boolean decideMachineForwardExecutionStatus(StateMachineInstance stateMachineInstance, Exception exp, + boolean specialPolicy) { + boolean result = false; + + if (stateMachineInstance.getStatus() == null || ExecutionStatus.RU.equals(stateMachineInstance.getStatus())) { + result = true; + + List stateList = stateMachineInstance.getStateList(); + + setMachineStatusBasedOnStateListAndException(stateMachineInstance, stateList, exp); + + if (specialPolicy && ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) { + for (StateInstance stateInstance : stateMachineInstance.getStateList()) { + if (!stateInstance.isIgnoreStatus() && (stateInstance.isForUpdate() || stateInstance + .isForCompensation())) { + stateMachineInstance.setStatus(ExecutionStatus.UN); + break; + } + } + if (ExecutionStatus.SU.equals(stateMachineInstance.getStatus())) { + stateMachineInstance.setStatus(ExecutionStatus.FA); + } + } + } + return result; + + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ExceptionUtils.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ExceptionUtils.java new file mode 100644 index 0000000..c2ae2d6 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ExceptionUtils.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.utils; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * Exception Utils + * + * @author lorne.cl + */ +public class ExceptionUtils { + + public static final String CONNECT_TIMED_OUT = "connect timed out"; + public static final String CONNECT_TIME_OUT_EXCEPTION_CLASS_NAME = "ConnectTimeoutException"; + public static final String READ_TIME_OUT_EXCEPTION_CLASS_NAME = "ReadTimeoutException"; + public static final String CONNECT_EXCEPTION_CLASS_NAME = "ConnectException"; + public static final int MAX_CAUSE_DEP = 20; + + public static EngineExecutionException createEngineExecutionException(Exception e, FrameworkErrorCode code, + String message, + StateMachineInstance stateMachineInstance, + StateInstance stateInstance) { + EngineExecutionException exception = new EngineExecutionException(e, message, code); + if (stateMachineInstance != null) { + exception.setStateMachineName(stateMachineInstance.getStateMachine().getAppName()); + exception.setStateMachineInstanceId(stateMachineInstance.getId()); + if (stateInstance != null) { + exception.setStateName(stateInstance.getName()); + exception.setStateInstanceId(stateInstance.getId()); + } + } + return exception; + } + + public static EngineExecutionException createEngineExecutionException(FrameworkErrorCode code, String message, + StateMachineInstance stateMachineInstance, + StateInstance stateInstance) { + + return createEngineExecutionException(null, code, message, stateMachineInstance, stateInstance); + } + + public static EngineExecutionException createEngineExecutionException(Exception e, FrameworkErrorCode code, + String message, + StateMachineInstance stateMachineInstance, + String stateName) { + EngineExecutionException exception = new EngineExecutionException(e, message, code); + if (stateMachineInstance != null) { + exception.setStateMachineName(stateMachineInstance.getStateMachine().getAppName()); + exception.setStateMachineInstanceId(stateMachineInstance.getId()); + exception.setStateName(stateName); + } + return exception; + } + + /** + * getNetExceptionType + * + * @param throwable + * @return + */ + public static NetExceptionType getNetExceptionType(Throwable throwable) { + + Throwable currentCause = throwable; + + int dep = MAX_CAUSE_DEP; + + while (currentCause != null && dep > 0) { + + if (currentCause instanceof java.net.SocketTimeoutException) { + if (CONNECT_TIMED_OUT.equals(currentCause.getMessage())) { + return NetExceptionType.CONNECT_TIMEOUT_EXCEPTION; + } else { + return NetExceptionType.READ_TIMEOUT_EXCEPTION; + } + } else if (currentCause instanceof java.net.ConnectException) { + return NetExceptionType.CONNECT_EXCEPTION; + } else if (currentCause.getClass().getSimpleName().contains(CONNECT_TIME_OUT_EXCEPTION_CLASS_NAME)) { + return NetExceptionType.CONNECT_TIMEOUT_EXCEPTION; + } else if (currentCause.getClass().getSimpleName().contains(READ_TIME_OUT_EXCEPTION_CLASS_NAME)) { + return NetExceptionType.READ_TIMEOUT_EXCEPTION; + } else if (currentCause.getClass().getSimpleName().contains(CONNECT_EXCEPTION_CLASS_NAME)) { + return NetExceptionType.CONNECT_EXCEPTION; + } else { + Throwable parentCause = currentCause.getCause(); + if (parentCause == null || parentCause == currentCause) { + break; + } + currentCause = parentCause; + dep--; + } + } + return NetExceptionType.NOT_NET_EXCEPTION; + } + + public enum NetExceptionType { + /** + * Exception occurred while creating connection + */ + CONNECT_EXCEPTION, + /** + * create connection timeout + */ + CONNECT_TIMEOUT_EXCEPTION, + /** + * read timeout from remote(request has sent) + */ + READ_TIMEOUT_EXCEPTION, + /** + * not a network exception + */ + NOT_NET_EXCEPTION + } + + /** + * Determine if the it is network exception + * @param throwable + * @return + */ + public static boolean isNetException(Throwable throwable) { + NetExceptionType netExceptionType = getNetExceptionType(throwable); + return netExceptionType != null && netExceptionType != NetExceptionType.NOT_NET_EXCEPTION; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ProcessContextBuilder.java b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ProcessContextBuilder.java new file mode 100644 index 0000000..fac7a27 --- /dev/null +++ b/saga/seata-saga-engine/src/main/java/io/seata/saga/engine/utils/ProcessContextBuilder.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.engine.utils; + +import java.util.Map; + +import io.seata.saga.engine.AsyncCallback; +import io.seata.saga.engine.StateMachineConfig; +import io.seata.saga.engine.StateMachineEngine; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessType; +import io.seata.saga.proctrl.impl.ProcessContextImpl; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * Process Context Builder + * + * @author lorne.cl + */ +public class ProcessContextBuilder { + + private ProcessContextImpl processContext; + + private ProcessContextBuilder() { + this.processContext = new ProcessContextImpl(); + } + + public static ProcessContextBuilder create() { + return new ProcessContextBuilder(); + } + + public ProcessContext build() { + return processContext; + } + + public ProcessContextBuilder withProcessType(ProcessType processType) { + if (processType != null) { + this.processContext.setVariable(ProcessContext.VAR_NAME_PROCESS_TYPE, processType); + } + return this; + } + + public ProcessContextBuilder withAsyncCallback(AsyncCallback asyncCallback) { + if (asyncCallback != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_ASYNC_CALLBACK, asyncCallback); + } + return this; + } + + public ProcessContextBuilder withInstruction(Instruction instruction) { + if (instruction != null) { + this.processContext.setInstruction(instruction); + } + return this; + } + + public ProcessContextBuilder withStateMachineInstance(StateMachineInstance stateMachineInstance) { + if (stateMachineInstance != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_INST, stateMachineInstance); + this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE, + stateMachineInstance.getStateMachine()); + } + return this; + } + + public ProcessContextBuilder withStateMachineEngine(StateMachineEngine stateMachineEngine) { + if (stateMachineEngine != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_ENGINE, stateMachineEngine); + } + return this; + } + + public ProcessContextBuilder withStateMachineConfig(StateMachineConfig stateMachineConfig) { + if (stateMachineConfig != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, stateMachineConfig); + } + return this; + } + + public ProcessContextBuilder withStateMachineContextVariables(Map contextVariables) { + if (contextVariables != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONTEXT, contextVariables); + } + return this; + } + + public ProcessContextBuilder withOperationName(String operationName) { + if (operationName != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_OPERATION_NAME, operationName); + } + return this; + } + + public ProcessContextBuilder withStateInstance(StateInstance stateInstance) { + if (stateInstance != null) { + this.processContext.setVariable(DomainConstants.VAR_NAME_STATE_INST, stateInstance); + } + return this; + } + + public ProcessContextBuilder withIsAsyncExecution(boolean isAsyncExecution) { + this.processContext.setVariable(DomainConstants.VAR_NAME_IS_ASYNC_EXECUTION, isAsyncExecution); + return this; + } +} \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor b/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor new file mode 100644 index 0000000..43ee097 --- /dev/null +++ b/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateHandlerInterceptor @@ -0,0 +1,3 @@ +io.seata.saga.engine.pcext.interceptors.ServiceTaskHandlerInterceptor +io.seata.saga.engine.pcext.interceptors.ScriptTaskHandlerInterceptor +io.seata.saga.engine.pcext.interceptors.LoopTaskHandlerInterceptor \ No newline at end of file diff --git a/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateRouterInterceptor b/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateRouterInterceptor new file mode 100644 index 0000000..7085ba7 --- /dev/null +++ b/saga/seata-saga-engine/src/main/resources/META-INF/services/io.seata.saga.engine.pcext.StateRouterInterceptor @@ -0,0 +1 @@ +io.seata.saga.engine.pcext.interceptors.EndStateRouterInterceptor \ No newline at end of file diff --git a/saga/seata-saga-processctrl/pom.xml b/saga/seata-saga-processctrl/pom.xml new file mode 100644 index 0000000..253f10c --- /dev/null +++ b/saga/seata-saga-processctrl/pom.xml @@ -0,0 +1,31 @@ + + + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-processctrl ${project.version} + seata-saga-processctrl + jar + + \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/HierarchicalProcessContext.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/HierarchicalProcessContext.java new file mode 100644 index 0000000..bb21fa3 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/HierarchicalProcessContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +import java.util.Map; + +/** + * Hierarchical process context + * + * @author lorne.cl + */ +public interface HierarchicalProcessContext extends ProcessContext { + + /** + * Gets get variable locally. + * + * @param name the name + * @return the get variable locally + */ + Object getVariableLocally(String name); + + /** + * Sets set variable locally. + * + * @param name the name + * @param value the value + */ + void setVariableLocally(String name, Object value); + + /** + * Gets get variables locally. + * + * @return the get variables locally + */ + Map getVariablesLocally(); + + /** + * Sets set variables locally. + * + * @param variables the variables + */ + void setVariablesLocally(Map variables); + + /** + * Has variable local boolean. + * + * @param name the name + * @return the boolean + */ + boolean hasVariableLocal(String name); + + /** + * Remove variable locally. + * + * @param name the name + * @return the removed variable or null + */ + Object removeVariableLocally(String name); + + /** + * Clear locally. + */ + void clearLocally(); +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/Instruction.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/Instruction.java new file mode 100644 index 0000000..b9aa015 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/Instruction.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +/** + * Instruction + * + * @author jin.xie + * @author xi.chen + * @author lorne.cl + */ +public interface Instruction { + +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessContext.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessContext.java new file mode 100644 index 0000000..48e9886 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessContext.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +import java.util.Map; + +/** + * Process Context + * + * @author jin.xie + * @author lorne.cl + */ +public interface ProcessContext { + + String VAR_NAME_PROCESS_TYPE = "_ProcessType_"; + + /** + * Gets get variable. + * + * @param name the name + * @return the get variable + */ + Object getVariable(String name); + + /** + * Sets set variable. + * + * @param name the name + * @param value the value + */ + void setVariable(String name, Object value); + + /** + * Gets get variables. + * + * @return the get variables + */ + Map getVariables(); + + /** + * Sets set variables. + * + * @param variables the variables + */ + void setVariables(Map variables); + + /** + * Remove variable. + * + * @param name the name + * @return the removed variable or null + */ + Object removeVariable(String name); + + /** + * Has variable boolean. + * + * @param name the name + * @return the boolean + */ + boolean hasVariable(String name); + + /** + * Gets get instruction. + * + * @return the get instruction + */ + Instruction getInstruction(); + + /** + * Sets set instruction. + * + * @param instruction the instruction + */ + void setInstruction(Instruction instruction); + + /** + * Gets get instruction. + * + * @param the type parameter + * @param clazz the clazz + * @return the get instruction + */ + T getInstruction(Class clazz); +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessController.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessController.java new file mode 100644 index 0000000..89f2991 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessController.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +import io.seata.common.exception.FrameworkException; + +/** + * Process Controller + * + * @author jin.xie + * @author lorne.cl + */ +public interface ProcessController { + + /** + * process business logic + * + * @param context + * @throws FrameworkException + */ + void process(ProcessContext context) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessRouter.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessRouter.java new file mode 100644 index 0000000..2a277b8 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessRouter.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +import io.seata.common.exception.FrameworkException; + +/** + * Process Router + * + * @author jin.xie + * @author lorne.cl + */ +public interface ProcessRouter { + + /** + * route + * + * @param context + * @return + * @throws FrameworkException + */ + Instruction route(ProcessContext context) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessType.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessType.java new file mode 100644 index 0000000..9207eb5 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/ProcessType.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +/** + * Process type + * + * @author jin.xie + * @author lorne.cl + */ +public enum ProcessType { + + /** + * SEATA State Language + */ + STATE_LANG("STATE_LANG", "SEATA State Language"); + + private String code; + + private String message; + + ProcessType(String code, String message) { + this.code = code; + this.message = message; + } + + /** + * get enum by code + * + * @param code + * @return + */ + public static ProcessType getEnumByCode(String code) { + for (ProcessType codetmp : ProcessType.values()) { + if (codetmp.getCode().equalsIgnoreCase(code)) { + return codetmp; + } + } + return null; + } + + /** + * get code + * + * @return + */ + public String getCode() { + return code; + } + + /** + * get message + * + * @return + */ + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventBus.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventBus.java new file mode 100644 index 0000000..b6e092f --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventBus.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing; + +import java.util.List; + +import io.seata.common.exception.FrameworkException; + +/** + * Event bus + * + * @author lorne.cl + */ +public interface EventBus { + + /** + * insert add element into bus + * + * @param e + * @return + * @throws FrameworkException + */ + boolean offer(E e) throws FrameworkException; + + /** + * get event consumers + * + * @param clazz + * @return + */ + List getEventConsumers(Class clazz); + + /** + * register event consumer + * + * @param eventConsumer + */ + void registerEventConsumer(EventConsumer eventConsumer); +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventConsumer.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventConsumer.java new file mode 100644 index 0000000..4cf366d --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventConsumer.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing; + +import io.seata.common.exception.FrameworkException; + +/** + * Event Consumer + * + * @author lorne.cl + */ +public interface EventConsumer { + + /** + * process + * + * @param event + * @throws FrameworkException + */ + void process(E event) throws FrameworkException; + + /** + * if thd handler can handle this class return true otherwise return false + * + * @param clazz + * @return + */ + boolean accept(Class clazz); +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventPublisher.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventPublisher.java new file mode 100644 index 0000000..fe6c7dc --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/EventPublisher.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing; + +import io.seata.common.exception.FrameworkException; + +/** + * Event publisher + * + * @author lorne.cl + */ +public interface EventPublisher { + + boolean publish(E event) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AbstractEventBus.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AbstractEventBus.java new file mode 100644 index 0000000..fc6950e --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AbstractEventBus.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing.impl; + +import java.util.ArrayList; +import java.util.List; + +import io.seata.saga.proctrl.eventing.EventBus; +import io.seata.saga.proctrl.eventing.EventConsumer; + +/** + * Abstract Event Bus + * + * @author jin.xie + * @author lorne.cl + */ +public abstract class AbstractEventBus implements EventBus { + + private List eventConsumerList = new ArrayList<>(); + + @Override + public List getEventConsumers(Class clazz) { + + List acceptedConsumers = new ArrayList<>(); + for (EventConsumer eventConsumer : eventConsumerList) { + if (eventConsumer.accept(clazz)) { + acceptedConsumers.add(eventConsumer); + } + } + return acceptedConsumers; + } + + @Override + public void registerEventConsumer(EventConsumer eventConsumer) { + eventConsumerList.add(eventConsumer); + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AsyncEventBus.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AsyncEventBus.java new file mode 100644 index 0000000..e4cdc25 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/AsyncEventBus.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing.impl; + +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.eventing.EventConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Asynchronized EventBus + * + * @author lorne.cl + */ +public class AsyncEventBus extends AbstractEventBus { + + private static final Logger LOGGER = LoggerFactory.getLogger(AsyncEventBus.class); + + private ThreadPoolExecutor threadPoolExecutor; + + @Override + public boolean offer(ProcessContext context) throws FrameworkException { + List eventConsumers = getEventConsumers(context.getClass()); + if (CollectionUtils.isEmpty(eventConsumers)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("cannot find event handler by class: " + context.getClass()); + } + return false; + } + + for (EventConsumer eventConsumer : eventConsumers) { + threadPoolExecutor.execute(() -> eventConsumer.process(context)); + } + return true; + } + + public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) { + this.threadPoolExecutor = threadPoolExecutor; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/DirectEventBus.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/DirectEventBus.java new file mode 100644 index 0000000..ca28c45 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/DirectEventBus.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing.impl; + +import java.util.List; +import java.util.Stack; + +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.eventing.EventConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Deliver event to event consumer directly + * + * @author lorne.cl + */ +public class DirectEventBus extends AbstractEventBus { + + private static final Logger LOGGER = LoggerFactory.getLogger(DirectEventBus.class); + + private static final String VAR_NAME_SYNC_EXE_STACK = "_sync_execution_stack_"; + + @Override + public boolean offer(ProcessContext context) throws FrameworkException { + List eventHandlers = getEventConsumers(context.getClass()); + if (CollectionUtils.isEmpty(eventHandlers)) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("cannot find event handler by class: " + context.getClass()); + } + return false; + } + + boolean isFirstEvent = false; + Stack currentStack = (Stack)context.getVariable(VAR_NAME_SYNC_EXE_STACK); + if (currentStack == null) { + synchronized (context) { + currentStack = (Stack)context.getVariable(VAR_NAME_SYNC_EXE_STACK); + if (currentStack == null) { + currentStack = new Stack<>(); + context.setVariable(VAR_NAME_SYNC_EXE_STACK, currentStack); + isFirstEvent = true; + } + } + } + + currentStack.push(context); + + if (isFirstEvent) { + try { + while (currentStack.size() > 0) { + ProcessContext currentContext = currentStack.pop(); + for (EventConsumer eventHandler : eventHandlers) { + eventHandler.process(currentContext); + } + } + } finally { + context.removeVariable(VAR_NAME_SYNC_EXE_STACK); + } + } + return true; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventConsumer.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventConsumer.java new file mode 100644 index 0000000..c6b0438 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventConsumer.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing.impl; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessController; +import io.seata.saga.proctrl.eventing.EventConsumer; + +/** + * ProcessCtrl Event Consumer + * + * @author lorne.cl + */ +public class ProcessCtrlEventConsumer implements EventConsumer { + + private ProcessController processController; + + @Override + public void process(ProcessContext event) throws FrameworkException { + + processController.process(event); + } + + @Override + public boolean accept(Class clazz) { + return ProcessContext.class.isAssignableFrom(clazz); + } + + public void setProcessController(ProcessController processController) { + this.processController = processController; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java new file mode 100644 index 0000000..31c6ac4 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.eventing.impl; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.eventing.EventBus; +import io.seata.saga.proctrl.eventing.EventPublisher; + +/** + * ProcessCtrl Event Pulisher + * + * @author lorne.cl + */ +public class ProcessCtrlEventPublisher implements EventPublisher { + + private EventBus eventBus; + + @Override + public boolean publish(ProcessContext event) throws FrameworkException { + return eventBus.offer(event); + } + + public void setEventBus(EventBus eventBus) { + this.eventBus = eventBus; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/DefaultRouterHandler.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/DefaultRouterHandler.java new file mode 100644 index 0000000..b4910db --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/DefaultRouterHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.handler; + +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessRouter; +import io.seata.saga.proctrl.ProcessType; +import io.seata.saga.proctrl.eventing.EventPublisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default Router handler + * + * @author jin.xie + * @author lorne.cl + */ +public class DefaultRouterHandler implements RouterHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRouterHandler.class); + + private EventPublisher eventPublisher; + private Map processRouters; + + public static ProcessType matchProcessType(ProcessContext context) { + ProcessType processType = (ProcessType)context.getVariable(ProcessContext.VAR_NAME_PROCESS_TYPE); + if (processType == null) { + processType = ProcessType.STATE_LANG; + } + return processType; + } + + @Override + public void route(ProcessContext context) throws FrameworkException { + + try { + ProcessType processType = matchProcessType(context); + if (processType == null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Process type not found, context= {}", context); + } + throw new FrameworkException(FrameworkErrorCode.ProcessTypeNotFound); + } + + ProcessRouter processRouter = processRouters.get(processType.getCode()); + if (processRouter == null) { + LOGGER.error("Cannot find process router by type {}, context = {}", processType.getCode(), context); + throw new FrameworkException(FrameworkErrorCode.ProcessRouterNotFound); + } + + Instruction instruction = processRouter.route(context); + if (instruction == null) { + LOGGER.info("route instruction is null, process end"); + } else { + context.setInstruction(instruction); + + eventPublisher.publish(context); + } + } catch (FrameworkException e) { + throw e; + } catch (Exception ex) { + throw new FrameworkException(ex, ex.getMessage(), FrameworkErrorCode.UnknownAppError); + } + } + + public void setEventPublisher(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + public void setProcessRouters(Map processRouters) { + this.processRouters = processRouters; + } +} diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/ProcessHandler.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/ProcessHandler.java new file mode 100644 index 0000000..e626929 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/ProcessHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.handler; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; + +/** + * Process Handler + * + * @author jin.xie + * @author lorne.cl + */ +public interface ProcessHandler { + + /** + * process + * + * @param context + * @throws FrameworkException + */ + void process(ProcessContext context) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/RouterHandler.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/RouterHandler.java new file mode 100644 index 0000000..2894d96 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/handler/RouterHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.handler; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; + +/** + * Router Handler + * + * @author jin.xie + * @author lorne.cl + */ +public interface RouterHandler { + + /** + * route + * + * @param context + * @throws FrameworkException + */ + void route(ProcessContext context) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessContextImpl.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessContextImpl.java new file mode 100644 index 0000000..2495d5b --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessContextImpl.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.saga.proctrl.HierarchicalProcessContext; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; + +/** + * The default process context implementation + * + * @author lorne.cl + */ +public class ProcessContextImpl implements HierarchicalProcessContext, ProcessContext { + + private Map variables = new ConcurrentHashMap<>(); + private Instruction instruction; + private ProcessContext parent; + + @Override + public Object getVariable(String name) { + if (variables.containsKey(name)) { + return variables.get(name); + } + + if (parent != null) { + return parent.getVariable(name); + } + + return null; + } + + @Override + public void setVariable(String name, Object value) { + if (variables.containsKey(name)) { + setVariableLocally(name, value); + } else { + if (parent != null) { + parent.setVariable(name, value); + } else { + setVariableLocally(name, value); + } + } + } + + @Override + public Map getVariables() { + final Map collectedVariables = new HashMap<>(); + + if (parent != null) { + collectedVariables.putAll(parent.getVariables()); + } + variables.forEach(collectedVariables::put); + return collectedVariables; + } + + @Override + public void setVariables(final Map variables) { + if (variables != null) { + variables.forEach(this::setVariable); + } + } + + @Override + public Object getVariableLocally(String name) { + return variables.get(name); + } + + @Override + public void setVariableLocally(String name, Object value) { + variables.put(name, value); + } + + @Override + public Map getVariablesLocally() { + return Collections.unmodifiableMap(variables); + } + + @Override + public void setVariablesLocally(Map variables) { + this.variables.putAll(variables); + } + + @Override + public boolean hasVariable(String name) { + if (variables.containsKey(name)) { + return true; + } + if (parent != null) { + return parent.hasVariable(name); + } + return false; + } + + @Override + public Instruction getInstruction() { + return instruction; + } + + @Override + public void setInstruction(Instruction instruction) { + this.instruction = instruction; + } + + @Override + public T getInstruction(Class clazz) { + return (T)instruction; + } + + @Override + public boolean hasVariableLocal(String name) { + return variables.containsKey(name); + } + + @Override + public Object removeVariable(String name) { + if (variables.containsKey(name)) { + return variables.remove(name); + } + + if (parent != null) { + return parent.removeVariable(name); + } + + return null; + } + + @Override + public Object removeVariableLocally(String name) { + return variables.remove(name); + } + + @Override + public void clearLocally() { + variables.clear(); + } + + public ProcessContext getParent() { + return parent; + } + + public void setParent(ProcessContext parent) { + this.parent = parent; + } + + @Override + public String toString() { + return "{" + "variables=" + variables + ", instruction=" + instruction + ", parent=" + parent + '}'; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessControllerImpl.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessControllerImpl.java new file mode 100644 index 0000000..6361369 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/impl/ProcessControllerImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.impl; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessController; +import io.seata.saga.proctrl.process.BusinessProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Default implementation of Process controller + * + * @author jin.xie + * @author lorne.cl + */ +public class ProcessControllerImpl implements ProcessController { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProcessControllerImpl.class); + + private BusinessProcessor businessProcessor; + + @Override + public void process(ProcessContext context) throws FrameworkException { + + try { + + businessProcessor.process(context); + + businessProcessor.route(context); + + } catch (FrameworkException fex) { + throw fex; + } catch (Exception ex) { + LOGGER.error("Unknown exception occurred, context = {}", context, ex); + throw new FrameworkException(ex, "Unknown exception occurred", FrameworkErrorCode.UnknownAppError); + } + } + + public void setBusinessProcessor(BusinessProcessor businessProcessor) { + this.businessProcessor = businessProcessor; + } +} diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/BusinessProcessor.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/BusinessProcessor.java new file mode 100644 index 0000000..229aa6d --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/BusinessProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.process; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; + +/** + * Business logic processor + * + * @author jin.xie + * @author lorne.cl + */ +public interface BusinessProcessor { + + /** + * process business logic + * + * @param context + * @throws FrameworkException + */ + void process(ProcessContext context) throws FrameworkException; + + /** + * route + * + * @param context + * @throws FrameworkException + */ + void route(ProcessContext context) throws FrameworkException; +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/impl/CustomizeBusinessProcessor.java b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/impl/CustomizeBusinessProcessor.java new file mode 100644 index 0000000..26b4b57 --- /dev/null +++ b/saga/seata-saga-processctrl/src/main/java/io/seata/saga/proctrl/process/impl/CustomizeBusinessProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.process.impl; + +import java.util.Map; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessType; +import io.seata.saga.proctrl.handler.ProcessHandler; +import io.seata.saga.proctrl.handler.RouterHandler; +import io.seata.saga.proctrl.process.BusinessProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Customizable Business Processor + * + * @author jin.xie + * @author lorne.cl + */ +public class CustomizeBusinessProcessor implements BusinessProcessor { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomizeBusinessProcessor.class); + + private Map processHandlers; + + private Map routerHandlers; + + public static ProcessType matchProcessType(ProcessContext context) { + ProcessType processType = (ProcessType)context.getVariable(ProcessContext.VAR_NAME_PROCESS_TYPE); + if (processType == null) { + processType = ProcessType.STATE_LANG; + } + return processType; + } + + @Override + public void process(ProcessContext context) throws FrameworkException { + + ProcessType processType = matchProcessType(context); + if (processType == null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Process type not found, context= {}", context); + } + throw new FrameworkException(FrameworkErrorCode.ProcessTypeNotFound); + } + + ProcessHandler processor = processHandlers.get(processType.getCode()); + if (processor == null) { + LOGGER.error("Cannot find process handler by type {}, context= {}", processType.getCode(), context); + throw new FrameworkException(FrameworkErrorCode.ProcessHandlerNotFound); + } + + processor.process(context); + } + + @Override + public void route(ProcessContext context) throws FrameworkException { + + ProcessType processType = matchProcessType(context); + if (processType == null) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Process type not found, the process is no longer advanced, context= {}", context); + } + return; + } + + RouterHandler router = routerHandlers.get(processType.getCode()); + if (router == null) { + LOGGER.error("Cannot find router handler by type {}, context= {}", processType.getCode(), context); + return; + } + + router.route(context); + } + + public void setProcessHandlers(Map processHandlers) { + this.processHandlers = processHandlers; + } + + public void setRouterHandlers(Map routerHandlers) { + this.routerHandlers = routerHandlers; + } +} diff --git a/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/ProcessControllerTests.java b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/ProcessControllerTests.java new file mode 100644 index 0000000..cb0f866 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/ProcessControllerTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import io.seata.saga.proctrl.eventing.impl.AsyncEventBus; +import io.seata.saga.proctrl.eventing.impl.DirectEventBus; +import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventConsumer; +import io.seata.saga.proctrl.eventing.impl.ProcessCtrlEventPublisher; +import io.seata.saga.proctrl.handler.DefaultRouterHandler; +import io.seata.saga.proctrl.handler.ProcessHandler; +import io.seata.saga.proctrl.handler.RouterHandler; +import io.seata.saga.proctrl.impl.ProcessContextImpl; +import io.seata.saga.proctrl.impl.ProcessControllerImpl; +import io.seata.saga.proctrl.mock.MockInstruction; +import io.seata.saga.proctrl.mock.MockProcessHandler; +import io.seata.saga.proctrl.mock.MockProcessRouter; +import io.seata.saga.proctrl.process.impl.CustomizeBusinessProcessor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * ProcessController Tests + * + * @author lorne.cl + */ +public class ProcessControllerTests { + + @Test + public void testSimpleProcessCtrl() { + + try { + ProcessCtrlEventPublisher processCtrlEventPublisher = buildEventPublisher(); + + ProcessContext context = new ProcessContextImpl(); + MockInstruction instruction = new MockInstruction(); + instruction.setTestString("one"); + context.setInstruction(instruction); + context.setVariable("TEST", "test"); + + processCtrlEventPublisher.publish(context); + } catch (Exception e) { + Assertions.fail(e); + } + } + + @Test + public void testSimpleProcessCtrlAsync() { + + try { + ProcessCtrlEventPublisher processCtrlEventPublisher = buildAsyncEventPublisher(); + + ProcessContext context = new ProcessContextImpl(); + MockInstruction instruction = new MockInstruction(); + instruction.setTestString("one"); + context.setInstruction(instruction); + context.setVariable("TEST", "test"); + + processCtrlEventPublisher.publish(context); + } catch (Exception e) { + Assertions.fail(e); + } + } + + private ProcessCtrlEventPublisher buildEventPublisher() throws Exception { + ProcessCtrlEventPublisher syncEventPublisher = new ProcessCtrlEventPublisher(); + + ProcessControllerImpl processorController = createProcessorController(syncEventPublisher); + + ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer(); + processCtrlEventConsumer.setProcessController(processorController); + + DirectEventBus directEventBus = new DirectEventBus(); + syncEventPublisher.setEventBus(directEventBus); + + directEventBus.registerEventConsumer(processCtrlEventConsumer); + + return syncEventPublisher; + } + + private ProcessCtrlEventPublisher buildAsyncEventPublisher() throws Exception { + ProcessCtrlEventPublisher asyncEventPublisher = new ProcessCtrlEventPublisher(); + + ProcessControllerImpl processorController = createProcessorController(asyncEventPublisher); + + ProcessCtrlEventConsumer processCtrlEventConsumer = new ProcessCtrlEventConsumer(); + processCtrlEventConsumer.setProcessController(processorController); + + AsyncEventBus asyncEventBus = new AsyncEventBus(); + asyncEventBus.setThreadPoolExecutor( + new ThreadPoolExecutor(1, 5, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>())); + + asyncEventPublisher.setEventBus(asyncEventBus); + + asyncEventBus.registerEventConsumer(processCtrlEventConsumer); + + return asyncEventPublisher; + } + + private ProcessControllerImpl createProcessorController(ProcessCtrlEventPublisher eventPublisher) throws Exception { + + DefaultRouterHandler defaultRouterHandler = new DefaultRouterHandler(); + defaultRouterHandler.setEventPublisher(eventPublisher); + + Map processRouterMap = new HashMap<>(1); + processRouterMap.put(ProcessType.STATE_LANG.getCode(), new MockProcessRouter()); + defaultRouterHandler.setProcessRouters(processRouterMap); + + CustomizeBusinessProcessor customizeBusinessProcessor = new CustomizeBusinessProcessor(); + + Map processHandlerMap = new HashMap<>(1); + processHandlerMap.put(ProcessType.STATE_LANG.getCode(), new MockProcessHandler()); + customizeBusinessProcessor.setProcessHandlers(processHandlerMap); + + Map routerHandlerMap = new HashMap<>(1); + routerHandlerMap.put(ProcessType.STATE_LANG.getCode(), defaultRouterHandler); + customizeBusinessProcessor.setRouterHandlers(routerHandlerMap); + + ProcessControllerImpl processorController = new ProcessControllerImpl(); + processorController.setBusinessProcessor(customizeBusinessProcessor); + + return processorController; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockInstruction.java b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockInstruction.java new file mode 100644 index 0000000..a92f154 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockInstruction.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.mock; + +import io.seata.saga.proctrl.Instruction; + +/** + * @author lorne.cl + */ +public class MockInstruction implements Instruction { + + private String testString; + + public String getTestString() { + return testString; + } + + public void setTestString(String testString) { + this.testString = testString; + } + + @Override + public String toString() { + return "MockInstruction{" + "testString='" + testString + '\'' + '}'; + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessHandler.java b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessHandler.java new file mode 100644 index 0000000..170a0c4 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.mock; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.handler.ProcessHandler; + +/** + * @author lorne.cl + */ +public class MockProcessHandler implements ProcessHandler { + + @Override + public void process(ProcessContext context) throws FrameworkException { + System.out.println("MockProcessHandler.process executed. context: " + context); + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessRouter.java b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessRouter.java new file mode 100644 index 0000000..7fbf5f2 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/io/seata/saga/proctrl/mock/MockProcessRouter.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.proctrl.mock; + +import io.seata.common.exception.FrameworkException; +import io.seata.saga.proctrl.Instruction; +import io.seata.saga.proctrl.ProcessContext; +import io.seata.saga.proctrl.ProcessRouter; + +/** + * @author lorne.cl + */ +public class MockProcessRouter implements ProcessRouter { + + @Override + public Instruction route(ProcessContext context) throws FrameworkException { + System.out.println("MockProcessRouter.route executed. context: " + context); + MockInstruction instruction = context.getInstruction(MockInstruction.class); + if (instruction != null) { + if ("one".equals(instruction.getTestString())) { + instruction.setTestString("two"); + } else if ("two".equals(instruction.getTestString())) { + instruction.setTestString("three"); + } else { + instruction.setTestString(null); + return null;//end process + } + } + return instruction; + } +} \ No newline at end of file diff --git a/saga/seata-saga-rm/pom.xml b/saga/seata-saga-rm/pom.xml new file mode 100644 index 0000000..4a7bd62 --- /dev/null +++ b/saga/seata-saga-rm/pom.xml @@ -0,0 +1,49 @@ + + + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-rm ${project.version} + seata-saga-rm + + + + ${project.groupId} + seata-core + ${project.version} + + + ${project.groupId} + seata-rm + ${project.version} + + + ${project.groupId} + seata-saga-engine + ${project.version} + + + + + \ No newline at end of file diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/RMHandlerSaga.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/RMHandlerSaga.java new file mode 100644 index 0000000..03fb657 --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/RMHandlerSaga.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.rm; + +import io.seata.core.model.BranchType; +import io.seata.core.model.ResourceManager; +import io.seata.core.protocol.transaction.UndoLogDeleteRequest; +import io.seata.rm.AbstractRMHandler; +import io.seata.rm.DefaultResourceManager; + +/** + * The type Rm handler SAGA. + * + * @author lorne.cl + */ +public class RMHandlerSaga extends AbstractRMHandler { + + @Override + public void handle(UndoLogDeleteRequest request) { + //DO nothing + } + + /** + * get SAGA resource manager + * + * @return + */ + @Override + protected ResourceManager getResourceManager() { + return DefaultResourceManager.get().getResourceManager(BranchType.SAGA); + } + + @Override + public BranchType getBranchType() { + return BranchType.SAGA; + } + +} diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java new file mode 100644 index 0000000..8a087c3 --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResource.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.rm; + +import io.seata.core.model.BranchType; +import io.seata.core.model.Resource; + +/** + * Saga resource (Only register application as a saga resource) + * + * @author lorne.cl + */ +public class SagaResource implements Resource { + + private String resourceGroupId; + + private String applicationId; + + /** + * Gets get resource group id. + * + * @return the get resource group id + */ + @Override + public String getResourceGroupId() { + return resourceGroupId; + } + + /** + * Sets set resource group id. + * + * @param resourceGroupId the resource group id + */ + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + /** + * Gets get resource id. + * + * @return the get resource id + */ + @Override + public String getResourceId() { + return applicationId + "#" + resourceGroupId; + } + + /** + * Gets get branch type. + * + * @return the get branch type + */ + @Override + public BranchType getBranchType() { + return BranchType.SAGA; + } + + /** + * Gets get application id. + * + * @return the get application id + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets set application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } +} \ No newline at end of file diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java new file mode 100644 index 0000000..b58120b --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/SagaResourceManager.java @@ -0,0 +1,164 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.rm; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.core.exception.TransactionException; +import io.seata.core.model.BranchStatus; +import io.seata.core.model.BranchType; +import io.seata.core.model.GlobalStatus; +import io.seata.core.model.Resource; +import io.seata.rm.AbstractResourceManager; +import io.seata.saga.engine.exception.EngineExecutionException; +import io.seata.saga.engine.exception.ForwardInvalidException; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.RecoverStrategy; +import io.seata.saga.statelang.domain.StateMachineInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Saga resource manager + * + * @author lorne.cl + */ +public class SagaResourceManager extends AbstractResourceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SagaResourceManager.class); + + /** + * Saga resource cache + */ + private Map sagaResourceCache = new ConcurrentHashMap<>(); + + /** + * Instantiates a new saga resource manager. + */ + public SagaResourceManager() { + } + + /** + * registry saga resource + * + * @param resource The resource to be managed. + */ + @Override + public void registerResource(Resource resource) { + SagaResource sagaResource = (SagaResource)resource; + sagaResourceCache.put(sagaResource.getResourceId(), sagaResource); + super.registerResource(sagaResource); + } + + @Override + public Map getManagedResources() { + return sagaResourceCache; + } + + /** + * SAGA branch commit + * + * @param branchType + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return + * @throws TransactionException + */ + @Override + public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + try { + StateMachineInstance machineInstance = StateMachineEngineHolder.getStateMachineEngine().forward(xid, null); + + if (ExecutionStatus.SU.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + return BranchStatus.PhaseTwo_Committed; + } else if (ExecutionStatus.SU.equals(machineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_Rollbacked; + } else if (ExecutionStatus.FA.equals(machineInstance.getCompensationStatus()) || ExecutionStatus.UN.equals( + machineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } else if (ExecutionStatus.FA.equals(machineInstance.getStatus()) + && machineInstance.getCompensationStatus() == null) { + return BranchStatus.PhaseOne_Failed; + } + + } catch (ForwardInvalidException e) { + LOGGER.error("StateMachine forward failed, xid: " + xid, e); + + //if StateMachineInstanceNotExists stop retry + if (FrameworkErrorCode.StateMachineInstanceNotExists.equals(e.getErrcode())) { + return BranchStatus.PhaseTwo_Committed; + } + } catch (Exception e) { + LOGGER.error("StateMachine forward failed, xid: " + xid, e); + } + return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } + + /** + * SAGA branch rollback + * + * @param branchType the branch type + * @param xid Transaction id. + * @param branchId Branch id. + * @param resourceId Resource id. + * @param applicationData Application data bind with this branch. + * @return + * @throws TransactionException + */ + @Override + public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId, + String applicationData) throws TransactionException { + try { + StateMachineInstance stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().reloadStateMachineInstance(xid); + if (stateMachineInstance == null) { + return BranchStatus.PhaseTwo_Rollbacked; + } + if (RecoverStrategy.Forward.equals(stateMachineInstance.getStateMachine().getRecoverStrategy()) + && (GlobalStatus.TimeoutRollbacking.name().equals(applicationData) + || GlobalStatus.TimeoutRollbackRetrying.name().equals(applicationData))) { + LOGGER.warn("Retry by custom recover strategy [Forward] on timeout, SAGA global[{}]", xid); + return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } + + stateMachineInstance = StateMachineEngineHolder.getStateMachineEngine().compensate(xid, + null); + if (ExecutionStatus.SU.equals(stateMachineInstance.getCompensationStatus())) { + return BranchStatus.PhaseTwo_Rollbacked; + } + } catch (EngineExecutionException e) { + LOGGER.error("StateMachine compensate failed, xid: " + xid, e); + + //if StateMachineInstanceNotExists stop retry + if (FrameworkErrorCode.StateMachineInstanceNotExists.equals(e.getErrcode())) { + return BranchStatus.PhaseTwo_Rollbacked; + } + } catch (Exception e) { + LOGGER.error("StateMachine compensate failed, xid: " + xid, e); + } + return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } + + @Override + public BranchType getBranchType() { + return BranchType.SAGA; + } +} diff --git a/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java new file mode 100644 index 0000000..971a281 --- /dev/null +++ b/saga/seata-saga-rm/src/main/java/io/seata/saga/rm/StateMachineEngineHolder.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.rm; + +import io.seata.saga.engine.StateMachineEngine; + +/** + * @author lorne.cl + */ +public class StateMachineEngineHolder { + + private static StateMachineEngine stateMachineEngine; + + public static StateMachineEngine getStateMachineEngine() { + return stateMachineEngine; + } + + public void setStateMachineEngine(StateMachineEngine smEngine) { + stateMachineEngine = smEngine; + } +} \ No newline at end of file diff --git a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager new file mode 100644 index 0000000..d52180c --- /dev/null +++ b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.core.model.ResourceManager @@ -0,0 +1,17 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.saga.rm.SagaResourceManager diff --git a/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler new file mode 100644 index 0000000..27711cc --- /dev/null +++ b/saga/seata-saga-rm/src/main/resources/META-INF/services/io.seata.rm.AbstractRMHandler @@ -0,0 +1,17 @@ +# +# Copyright 1999-2019 Seata.io Group. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +io.seata.saga.rm.RMHandlerSaga \ No newline at end of file diff --git a/saga/seata-saga-statelang/pom.xml b/saga/seata-saga-statelang/pom.xml new file mode 100644 index 0000000..3496edd --- /dev/null +++ b/saga/seata-saga-statelang/pom.xml @@ -0,0 +1,41 @@ + + + + + + seata-saga + io.seata + ${revision} + + 4.0.0 + seata-saga-statelang ${project.version} + seata-saga-statelang + + + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ChoiceState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ChoiceState.java new file mode 100644 index 0000000..74046be --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ChoiceState.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.List; + +/** + * Choice State, We can choose only one choice + * + * @author lorne.cl + */ +public interface ChoiceState extends State { + + /** + * get choices + * + * @return + */ + List getChoices(); + + /** + * default choice + * + * @return + */ + String getDefault(); + + /** + * Choice + */ + static interface Choice { + + String getExpression(); + + String getNext(); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensateSubStateMachineState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensateSubStateMachineState.java new file mode 100644 index 0000000..4f9e534 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensateSubStateMachineState.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Compensate SubStateMachine State + * + * @author lorne.cl + */ +public interface CompensateSubStateMachineState extends ServiceTaskState { + +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensationTriggerState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensationTriggerState.java new file mode 100644 index 0000000..1759305 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/CompensationTriggerState.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Compensation trigger State + * + * @author lorne.cl + */ +public interface CompensationTriggerState extends State { + +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java new file mode 100644 index 0000000..586fb8e --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/DomainConstants.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * State Language Domain Constants + * + * @author lorne.cl + */ +public interface DomainConstants { + + //region State Types + String STATE_TYPE_SERVICE_TASK = "ServiceTask"; + String STATE_TYPE_CHOICE = "Choice"; + String STATE_TYPE_FAIL = "Fail"; + String STATE_TYPE_SUCCEED = "Succeed"; + String STATE_TYPE_COMPENSATION_TRIGGER = "CompensationTrigger"; + String STATE_TYPE_SUB_STATE_MACHINE = "SubStateMachine"; + String STATE_TYPE_SUB_MACHINE_COMPENSATION = "CompensateSubMachine"; + String STATE_TYPE_SCRIPT_TASK = "ScriptTask"; + String STATE_TYPE_LOOP_START = "LoopStart"; + //endregion + + String COMPENSATE_SUB_MACHINE_STATE_NAME_PREFIX = "_compensate_sub_machine_state_"; + + //region Service Types + String SERVICE_TYPE_SPRING_BEAN = "SpringBean"; + //endregion + + //region System Variables + String VAR_NAME_STATEMACHINE_CONTEXT = "context"; + String VAR_NAME_INPUT_PARAMS = "inputParams"; + String VAR_NAME_OUTPUT_PARAMS = "outputParams"; + String VAR_NAME_CURRENT_EXCEPTION = "currentException";//exception of current state + String VAR_NAME_BUSINESSKEY = "_business_key_"; + String VAR_NAME_SUB_MACHINE_PARENT_ID = "_sub_machine_parent_id_"; + String VAR_NAME_CURRENT_CHOICE = "_current_choice_"; + String VAR_NAME_STATEMACHINE_ERROR_CODE = "_statemachine_error_code_"; + String VAR_NAME_STATEMACHINE_ERROR_MSG = "_statemachine_error_message_"; + String VAR_NAME_CURRENT_EXCEPTION_ROUTE = "_current_exception_route_"; + String VAR_NAME_STATEMACHINE = "_current_statemachine_"; + String VAR_NAME_STATEMACHINE_INST = "_current_statemachine_instance_"; + String VAR_NAME_STATEMACHINE_ENGINE = "_current_statemachine_engine_"; + String VAR_NAME_STATE_INST = "_current_state_instance_"; + String VAR_NAME_STATEMACHINE_CONFIG = "_statemachine_config_"; + String VAR_NAME_FAIL_END_STATE_FLAG = "_fail_end_state_flag_"; + String VAR_NAME_CURRENT_COMPENSATION_HOLDER = "_current_compensation_holder_"; + String VAR_NAME_RETRIED_STATE_INST_ID = "_retried_state_instance_id"; + String VAR_NAME_OPERATION_NAME = "_operation_name_"; + String VAR_NAME_ASYNC_CALLBACK = "_async_callback_"; + String VAR_NAME_CURRENT_COMPEN_TRIGGER_STATE = "_is_compensating_"; + String VAR_NAME_IS_EXCEPTION_NOT_CATCH = "_is_exception_not_catch_"; + String VAR_NAME_PARENT_ID = "_parent_id_"; + String VAR_NAME_SUB_STATEMACHINE_EXEC_STATUE = "_sub_statemachine_execution_status_"; + String VAR_NAME_IS_FOR_SUB_STATMACHINE_FORWARD = "_is_for_sub_statemachine_forward_"; + String VAR_NAME_FIRST_COMPENSATION_STATE_STARTED = "_first_compensation_state_started"; + String VAR_NAME_GLOBAL_TX = "_global_transaction_"; + String VAR_NAME_IS_ASYNC_EXECUTION = "_is_async_execution_"; + String VAR_NAME_IS_LOOP_STATE = "_is_loop_state_"; + String VAR_NAME_CURRENT_LOOP_CONTEXT_HOLDER = "_current_loop_context_holder_"; + //endregion + + // region of loop + String LOOP_COUNTER = "loopCounter"; + String LOOP_SEMAPHORE = "loopSemaphore"; + String LOOP_RESULT = "loopResult"; + String NUMBER_OF_INSTANCES = "nrOfInstances"; + String NUMBER_OF_ACTIVE_INSTANCES = "nrOfActiveInstances"; + String NUMBER_OF_COMPLETED_INSTANCES = "nrOfCompletedInstances"; + // endregion + + String OPERATION_NAME_START = "start"; + String OPERATION_NAME_FORWARD = "forward"; + String OPERATION_NAME_COMPENSATE = "compensate"; + + String SEQ_ENTITY_STATE_MACHINE = "STATE_MACHINE"; + String SEQ_ENTITY_STATE_MACHINE_INST = "STATE_MACHINE_INST"; + String SEQ_ENTITY_STATE_INST = "STATE_INST"; + + String EXPRESSION_TYPE_SEQUENCE = "Sequence"; + String EVALUATOR_TYPE_EXCEPTION = "Exception"; + + String SEPERATOR_PARENT_ID = ":"; + + String DEFAULT_JSON_PARSER = "fastjson"; +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/EndState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/EndState.java new file mode 100644 index 0000000..4b50d3d --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/EndState.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * End State + * + * @author lorne.cl + */ +public interface EndState extends State { + +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java new file mode 100644 index 0000000..35706fb --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ExecutionStatus.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Execution Status + * + * @author lorne.cl + */ +public enum ExecutionStatus { + + /** + * Running + */ + RU("Running"), + + /** + * Succeed + */ + SU("Succeed"), + + /** + * Failed + */ + FA("Failed"), + + /** + * Unknown + */ + UN("Unknown"), + + /** + * Skipped + */ + SK("Skipped"); + + private String statusString; + + private ExecutionStatus(String statusString) { + this.statusString = statusString; + } + + public String getStatusString() { + return statusString; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/FailEndState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/FailEndState.java new file mode 100644 index 0000000..fe347d8 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/FailEndState.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Fail End State + * + * @author lorne.cl + */ +public interface FailEndState extends EndState { + + /** + * error code + * + * @return + */ + String getErrorCode(); + + /** + * error message + * + * @return + */ + String getMessage(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/LoopStartState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/LoopStartState.java new file mode 100644 index 0000000..f86d30c --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/LoopStartState.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Loop Starter + * + * @author anselleeyy + */ +public interface LoopStartState extends State { + +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java new file mode 100644 index 0000000..2eff162 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/RecoverStrategy.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * Recover Strategy + * + * @author lorne.cl + */ +public enum RecoverStrategy { + + /** + * Compensate + */ + Compensate, + + /** + * Forward + */ + Forward; +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ScriptTaskState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ScriptTaskState.java new file mode 100644 index 0000000..8cac732 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ScriptTaskState.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * ScriptTask State, execute scripts + * + * @author lorne.cl + */ +public interface ScriptTaskState extends TaskState { + + /** + * get ScriptType such as groovy + * + * @return + */ + String getScriptType(); + + /** + * get ScriptContent + * + * @return + */ + String getScriptContent(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ServiceTaskState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ServiceTaskState.java new file mode 100644 index 0000000..d4c877c --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/ServiceTaskState.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.List; + +/** + * ServiceTask State, be used to invoke a service + * + * @author lorne.cl + */ +public interface ServiceTaskState extends TaskState { + + /** + * Service type: such as SpringBean, SOFA RPC, default is StringBean + * + * @return + */ + String getServiceType(); + + /** + * service name + * + * @return + */ + String getServiceName(); + + /** + * service method + * + * @return + */ + String getServiceMethod(); + + /** + * service method + * + * @return + */ + List getParameterTypes(); + + /** + * Is it necessary to persist the service execution log? default is true + * + * @return + */ + boolean isPersist(); + + /** + * Is update last retry execution log, default append new + * + * @return + */ + Boolean isRetryPersistModeUpdate(); + + /** + * Is update last compensate execution log, default append new + * + * @return + */ + Boolean isCompensatePersistModeUpdate(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/State.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/State.java new file mode 100644 index 0000000..1295905 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/State.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.Map; + +/** + * A State in StateMachine + * + * @author lorne.cl + */ +public interface State { + + /** + * name + * + * @return + */ + String getName(); + + /** + * comment + * + * @return + */ + String getComment(); + + /** + * type + * + * @return + */ + String getType(); + + /** + * next state name + * + * @return + */ + String getNext(); + + /** + * extension properties + * + * @return + */ + Map getExtensions(); + + /** + * state machine instance + * + * @return + */ + StateMachine getStateMachine(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateInstance.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateInstance.java new file mode 100644 index 0000000..d6a92fc --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateInstance.java @@ -0,0 +1,376 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.Date; + +/** + * State execution instance + * + * @author lorne.cl + */ +public interface StateInstance { + + /** + * id + * + * @return + */ + String getId(); + + /** + * set id + * + * @param id + */ + void setId(String id); + + /** + * get Machine InstanceId + * + * @return + */ + String getMachineInstanceId(); + + /** + * set Machine InstanceId + * + * @param machineInstanceId + */ + void setMachineInstanceId(String machineInstanceId); + + /** + * get name + * + * @return + */ + String getName(); + + /** + * set name + * + * @param name + */ + void setName(String name); + + /** + * get type + * + * @return + */ + String getType(); + + /** + * set type + * + * @param type + */ + void setType(String type); + + /** + * get service name + * + * @return + */ + String getServiceName(); + + /** + * set service name + * + * @param serviceName + */ + void setServiceName(String serviceName); + + /** + * get service method + * + * @return + */ + String getServiceMethod(); + + /** + * set service method + * + * @param serviceMethod + */ + void setServiceMethod(String serviceMethod); + + /** + * get service type + * + * @return + */ + String getServiceType(); + + /** + * get service type + * + * @param serviceType + */ + void setServiceType(String serviceType); + + /** + * get businessKey + * + * @return + */ + String getBusinessKey(); + + /** + * set business key + * + * @param businessKey + */ + void setBusinessKey(String businessKey); + + /** + * get start time + * + * @return + */ + Date getGmtStarted(); + + /** + * set start time + * + * @param gmtStarted + */ + void setGmtStarted(Date gmtStarted); + + /** + * get update time + * + * @return + */ + Date getGmtUpdated(); + + /** + * set update time + * + * @param gmtUpdated + */ + void setGmtUpdated(Date gmtUpdated); + + /** + * get end time + * + * @return + */ + Date getGmtEnd(); + + /** + * set end time + * + * @param gmtEnd + */ + void setGmtEnd(Date gmtEnd); + + /** + * Is this state task will update data? + * + * @return + */ + boolean isForUpdate(); + + /** + * setForUpdate + * + * @param forUpdate + */ + void setForUpdate(boolean forUpdate); + + /** + * get exception + * + * @return + */ + Exception getException(); + + /** + * set exception + * + * @param exception + */ + void setException(Exception exception); + + /** + * get input params + * + * @return + */ + Object getInputParams(); + + /** + * set inout params + * + * @param inputParams + */ + void setInputParams(Object inputParams); + + /** + * get output params + * + * @return + */ + Object getOutputParams(); + + /** + * Sets set output params. + * + * @param outputParams the output params + */ + void setOutputParams(Object outputParams); + + /** + * Gets get status. + * + * @return the get status + */ + ExecutionStatus getStatus(); + + /** + * Sets set status. + * + * @param status the status + */ + void setStatus(ExecutionStatus status); + + /** + * Gets get state id compensated for. + * + * @return the get state id compensated for + */ + String getStateIdCompensatedFor(); + + /** + * Sets set state id compensated for. + * + * @param stateIdCompensatedFor the state id compensated for + */ + void setStateIdCompensatedFor(String stateIdCompensatedFor); + + /** + * Gets get state id retried for. + * + * @return the get state id retried for + */ + String getStateIdRetriedFor(); + + /** + * Sets set state id retried for. + * + * @param stateIdRetriedFor the state id retried for + */ + void setStateIdRetriedFor(String stateIdRetriedFor); + + /** + * Gets get compensation state. + * + * @return the get compensation state + */ + StateInstance getCompensationState(); + + /** + * Sets set compensation state. + * + * @param compensationState the compensation state + */ + void setCompensationState(StateInstance compensationState); + + /** + * Gets get state machine instance. + * + * @return the get state machine instance + */ + StateMachineInstance getStateMachineInstance(); + + /** + * Sets set state machine instance. + * + * @param stateMachineInstance the state machine instance + */ + void setStateMachineInstance(StateMachineInstance stateMachineInstance); + + /** + * Is ignore status boolean. + * + * @return the boolean + */ + boolean isIgnoreStatus(); + + /** + * Sets set ignore status. + * + * @param ignoreStatus the ignore status + */ + void setIgnoreStatus(boolean ignoreStatus); + + /** + * Is for compensation boolean. + * + * @return the boolean + */ + boolean isForCompensation(); + + /** + * Gets get serialized input params. + * + * @return the get serialized input params + */ + Object getSerializedInputParams(); + + /** + * Sets set serialized input params. + * + * @param serializedInputParams the serialized input params + */ + void setSerializedInputParams(Object serializedInputParams); + + /** + * Gets get serialized output params. + * + * @return the get serialized output params + */ + Object getSerializedOutputParams(); + + /** + * Sets set serialized output params. + * + * @param serializedOutputParams the serialized output params + */ + void setSerializedOutputParams(Object serializedOutputParams); + + /** + * Gets get serialized exception. + * + * @return the get serialized exception + */ + Object getSerializedException(); + + /** + * Sets set serialized exception. + * + * @param serializedException the serialized exception + */ + void setSerializedException(Object serializedException); + + /** + * Gets get compensation status. + * + * @return the get compensation status + */ + ExecutionStatus getCompensationStatus(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachine.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachine.java new file mode 100644 index 0000000..080cb78 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachine.java @@ -0,0 +1,202 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.Date; +import java.util.Map; + +/** + * StateMachine + * + * @author lorne.cl + */ +public interface StateMachine { + + /** + * name + * + * @return + */ + String getName(); + + /** + * comment + * + * @return + */ + String getComment(); + + /** + * start state name + * + * @return + */ + String getStartState(); + + void setStartState(String startState); + + /** + * version + * + * @return + */ + String getVersion(); + + /** + * set version + * + * @param version + */ + void setVersion(String version); + + /** + * states + * + * @return + */ + Map getStates(); + + /** + * get state + * + * @param name + * @return + */ + State getState(String name); + + /** + * get id + * + * @return + */ + String getId(); + + void setId(String id); + + /** + * get tenantId + * + * @return + */ + String getTenantId(); + + /** + * set tenantId + * + * @param tenantId + */ + void setTenantId(String tenantId); + + /** + * app name + * + * @return + */ + String getAppName(); + + /** + * type, there is only one type: SSL(SEATA state language) + * + * @return + */ + String getType(); + + /** + * statue (Active|Inactive) + * + * @return + */ + Status getStatus(); + + /** + * recover strategy: prefer compensation or forward when error occurred + * + * @return + */ + RecoverStrategy getRecoverStrategy(); + + /** + * set RecoverStrategy + * + * @param recoverStrategy + */ + void setRecoverStrategy(RecoverStrategy recoverStrategy); + + /** + * Is it persist execution log to storage?, default true + * + * @return + */ + boolean isPersist(); + + /** + * Is update last retry execution log, default append new + * + * @return + */ + Boolean isRetryPersistModeUpdate(); + + /** + * Is update last compensate execution log, default append new + * + * @return + */ + Boolean isCompensatePersistModeUpdate(); + + /** + * State language text + * + * @return + */ + String getContent(); + + void setContent(String content); + + /** + * get create time + * + * @return + */ + Date getGmtCreate(); + + /** + * set create time + * + * @param date + */ + void setGmtCreate(Date date); + + enum Status { + /** + * Active + */ + AC("Active"), + /** + * Inactive + */ + IN("Inactive"); + + private String statusString; + + Status(String statusString) { + this.statusString = statusString; + } + + public String getStatusString() { + return statusString; + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java new file mode 100644 index 0000000..ea2110e --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/StateMachineInstance.java @@ -0,0 +1,330 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * StateMachine execution instance + * + * @author lorne.cl + */ +public interface StateMachineInstance { + + /** + * Gets get id. + * + * @return the get id + */ + String getId(); + + /** + * Sets set id. + * + * @param id the id + */ + void setId(String id); + + /** + * Gets get machine id. + * + * @return the get machine id + */ + String getMachineId(); + + /** + * Sets set machine id. + * + * @param machineId the machine id + */ + void setMachineId(String machineId); + + /** + * Gets get tenant id. + * + * @return the tenant id + */ + String getTenantId(); + + /** + * Sets set tenant id. + * + * @param tenantId the tenant id + */ + void setTenantId(String tenantId); + + /** + * Gets get parent id. + * + * @return the get parent id + */ + String getParentId(); + + /** + * Sets set parent id. + * + * @param parentId the parent id + */ + void setParentId(String parentId); + + /** + * Gets get gmt started. + * + * @return the get gmt started + */ + Date getGmtStarted(); + + /** + * Sets set gmt started. + * + * @param gmtStarted the gmt started + */ + void setGmtStarted(Date gmtStarted); + + /** + * Gets get gmt end. + * + * @return the get gmt end + */ + Date getGmtEnd(); + + /** + * Sets set gmt end. + * + * @param gmtEnd the gmt end + */ + void setGmtEnd(Date gmtEnd); + + /** + * Put state instance. + * + * @param stateId the state id + * @param stateInstance the state instance + */ + void putStateInstance(String stateId, StateInstance stateInstance); + + /** + * Gets get status. + * + * @return the get status + */ + ExecutionStatus getStatus(); + + /** + * Sets set status. + * + * @param status the status + */ + void setStatus(ExecutionStatus status); + + /** + * Gets get compensation status. + * + * @return the get compensation status + */ + ExecutionStatus getCompensationStatus(); + + /** + * Sets set compensation status. + * + * @param compensationStatus the compensation status + */ + void setCompensationStatus(ExecutionStatus compensationStatus); + + /** + * Is running boolean. + * + * @return the boolean + */ + boolean isRunning(); + + /** + * Sets set running. + * + * @param running the running + */ + void setRunning(boolean running); + + /** + * Gets get gmt updated. + * + * @return the get gmt updated + */ + Date getGmtUpdated(); + + /** + * Sets set gmt updated. + * + * @param gmtUpdated the gmt updated + */ + void setGmtUpdated(Date gmtUpdated); + + /** + * Gets get business key. + * + * @return the get business key + */ + String getBusinessKey(); + + /** + * Sets set business key. + * + * @param businessKey the business key + */ + void setBusinessKey(String businessKey); + + /** + * Gets get exception. + * + * @return the get exception + */ + Exception getException(); + + /** + * Sets set exception. + * + * @param exception the exception + */ + void setException(Exception exception); + + /** + * Gets get start params. + * + * @return the get start params + */ + Map getStartParams(); + + /** + * Sets set start params. + * + * @param startParams the start params + */ + void setStartParams(Map startParams); + + /** + * Gets get end params. + * + * @return the get end params + */ + Map getEndParams(); + + /** + * Sets set end params. + * + * @param endParams the end params + */ + void setEndParams(Map endParams); + + /** + * Gets get context. + * + * @return + */ + Map getContext(); + + /** + * Sets set context. + * + * @param context + */ + void setContext(Map context); + + /** + * Gets get state machine. + * + * @return the get state machine + */ + StateMachine getStateMachine(); + + /** + * Sets set state machine. + * + * @param stateMachine the state machine + */ + void setStateMachine(StateMachine stateMachine); + + /** + * Gets get state list. + * + * @return the get state list + */ + List getStateList(); + + /** + * Sets set state list. + * + * @param stateList the state list + */ + void setStateList(List stateList); + + /** + * Gets get state map. + * + * @return the get state map + */ + Map getStateMap(); + + /** + * Sets set state map. + * + * @param stateMap the state map + */ + void setStateMap(Map stateMap); + + /** + * Gets get serialized start params. + * + * @return the get serialized start params + */ + Object getSerializedStartParams(); + + /** + * Sets set serialized start params. + * + * @param serializedStartParams the serialized start params + */ + void setSerializedStartParams(Object serializedStartParams); + + /** + * Gets get serialized end params. + * + * @return the get serialized end params + */ + Object getSerializedEndParams(); + + /** + * Sets set serialized end params. + * + * @param serializedEndParams the serialized end params + */ + void setSerializedEndParams(Object serializedEndParams); + + /** + * Gets get serialized exception. + * + * @return the get serialized exception + */ + Object getSerializedException(); + + /** + * Sets set serialized exception. + * + * @param serializedException the serialized exception + */ + void setSerializedException(Object serializedException); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SubStateMachine.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SubStateMachine.java new file mode 100644 index 0000000..9c79525 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SubStateMachine.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * SubStateMachine + * + * @author lorne.cl + * @see TaskState + */ +public interface SubStateMachine extends TaskState { + + /** + * state machine name + * + * @return + */ + String getStateMachineName(); + + /** + * Get compensate state object + * + * @return + */ + TaskState getCompensateStateObject(); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SucceedEndState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SucceedEndState.java new file mode 100644 index 0000000..9e8856c --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/SucceedEndState.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +/** + * SucceedEndState + * + * @author lorne.cl + */ +public interface SucceedEndState extends EndState { + +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/TaskState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/TaskState.java new file mode 100644 index 0000000..a708d65 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/TaskState.java @@ -0,0 +1,224 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain; + +import java.util.List; +import java.util.Map; + +/** + * A state used to execute a task + * + * @author lorne.cl + */ +public interface TaskState extends State { + + /** + * get compensate state + * + * @return + */ + String getCompensateState(); + + /** + * Is this state is used to compensate an other state, default false + * + * @return + */ + boolean isForCompensation(); + + /** + * Is this state will update data? default false + * + * @return + */ + boolean isForUpdate(); + + /** + * retry strategy + * + * @return + */ + List getRetry(); + + /** + * exception handling strategy + * + * @return + */ + List getCatches(); + + /** + * Execution state determination rule + * + * @return + */ + Map getStatus(); + + /** + * loop strategy + * + * @return + */ + Loop getLoop(); + + /** + * retry strategy + */ + interface Retry { + + /** + * exceptions + * + * @return + */ + List getExceptions(); + + /** + * exception classes + * + * @return + */ + List> getExceptionClasses(); + + /** + * set exception classes + * @param exceptionClasses + */ + void setExceptionClasses(List> exceptionClasses); + + /** + * getIntervalSeconds + * + * @return + */ + double getIntervalSeconds(); + + /** + * getMaxAttempts + * + * @return + */ + int getMaxAttempts(); + + /** + * get BackoffRate, default 1 + * + * @return + */ + double getBackoffRate(); + } + + /** + * exception match + */ + interface ExceptionMatch { + + /** + * exceptions + * + * @return + */ + List getExceptions(); + + /** + * exception classes + * + * @return + */ + List> getExceptionClasses(); + + /** + * set exception classes + * @param exceptionClasses + */ + void setExceptionClasses(List> exceptionClasses); + + /** + * next state name + * + * @return + */ + String getNext(); + } + + /** + * status match + */ + interface StatusMatch { + + /** + * status + * + * @return + */ + ExecutionStatus getStatus(); + + /** + * expression + * + * @return + */ + String getExpression(); + + /** + * expression type, default(SpringEL)|exception + * + * @return + */ + String getExpressionType(); + } + + /** + * loop strategy + */ + interface Loop { + + /** + * parallel size, default 1 + * + * @return + */ + int getParallel(); + + /** + * collection object name + * + * @return + */ + String getCollection(); + + /** + * element variable name + * + * @return + */ + String getElementVariableName(); + + /** + * element variable index name, default loopCounter + * + * @return + */ + String getElementIndexName(); + + /** + * completion condition, default nrOfInstances == nrOfCompletedInstances + * + * @return + */ + String getCompletionCondition(); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/AbstractTaskState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/AbstractTaskState.java new file mode 100644 index 0000000..e509cd9 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/AbstractTaskState.java @@ -0,0 +1,312 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.List; +import java.util.Map; + +import io.seata.common.util.StringUtils; +import io.seata.saga.statelang.domain.TaskState; + +/** + * The state of the execution task (abstract class), the specific task to be executed is determined by the subclass + * + * @author lorne.cl + */ +public abstract class AbstractTaskState extends BaseState implements TaskState { + + private String compensateState; + private boolean isForCompensation; + private boolean isForUpdate; + private List retry; + private List catches; + private List input; + private Map output; + private Map status;//Map + private List inputExpressions; + private Map outputExpressions; + private boolean isPersist = true; + private Boolean retryPersistModeUpdate; + private Boolean compensatePersistModeUpdate; + private Loop loop; + + @Override + public String getCompensateState() { + return compensateState; + } + + public void setCompensateState(String compensateState) { + this.compensateState = compensateState; + + if (StringUtils.isNotBlank(this.compensateState)) { + setForUpdate(true); + } + } + + @Override + public boolean isForCompensation() { + return isForCompensation; + } + + public void setForCompensation(boolean isForCompensation) { + this.isForCompensation = isForCompensation; + } + + @Override + public boolean isForUpdate() { + return this.isForUpdate; + } + + public void setForUpdate(boolean isForUpdate) { + this.isForUpdate = isForUpdate; + } + + @Override + public List getRetry() { + return retry; + } + + public void setRetry(List retry) { + this.retry = retry; + } + + @Override + public List getCatches() { + return catches; + } + + public void setCatches(List catches) { + this.catches = catches; + } + + public List getInput() { + return input; + } + + public void setInput(List input) { + this.input = input; + } + + public Map getOutput() { + return output; + } + + public void setOutput(Map output) { + this.output = output; + } + + public boolean isPersist() { + return isPersist; + } + + public void setPersist(boolean persist) { + isPersist = persist; + } + + public Boolean isRetryPersistModeUpdate() { + return retryPersistModeUpdate; + } + + public void setRetryPersistModeUpdate(Boolean retryPersistModeUpdate) { + this.retryPersistModeUpdate = retryPersistModeUpdate; + } + + public Boolean isCompensatePersistModeUpdate() { + return compensatePersistModeUpdate; + } + + public void setCompensatePersistModeUpdate(Boolean compensatePersistModeUpdate) { + this.compensatePersistModeUpdate = compensatePersistModeUpdate; + } + + public List getInputExpressions() { + return inputExpressions; + } + + public void setInputExpressions(List inputExpressions) { + this.inputExpressions = inputExpressions; + } + + public Map getOutputExpressions() { + return outputExpressions; + } + + public void setOutputExpressions(Map outputExpressions) { + this.outputExpressions = outputExpressions; + } + + @Override + public Map getStatus() { + return status; + } + + public void setStatus(Map status) { + this.status = status; + } + + @Override + public Loop getLoop() { + return loop; + } + + public void setLoop(Loop loop) { + this.loop = loop; + } + + public static class RetryImpl implements Retry { + + private List exceptions; + private List> exceptionClasses; + private double intervalSeconds; + private int maxAttempts; + private double backoffRate; + + @Override + public List getExceptions() { + return exceptions; + } + + public void setExceptions(List exceptions) { + this.exceptions = exceptions; + } + + @Override + public List> getExceptionClasses() { + return exceptionClasses; + } + + @Override + public void setExceptionClasses(List> exceptionClasses) { + this.exceptionClasses = exceptionClasses; + } + + @Override + public double getIntervalSeconds() { + return intervalSeconds; + } + + public void setIntervalSeconds(double intervalSeconds) { + this.intervalSeconds = intervalSeconds; + } + + @Override + public int getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + @Override + public double getBackoffRate() { + return backoffRate; + } + + public void setBackoffRate(double backoffRate) { + this.backoffRate = backoffRate; + } + } + + public static class ExceptionMatchImpl implements ExceptionMatch { + + List exceptions; + List> exceptionClasses; + String next; + + @Override + public List getExceptions() { + return exceptions; + } + + public void setExceptions(List exceptions) { + this.exceptions = exceptions; + } + + @Override + public List> getExceptionClasses() { + return exceptionClasses; + } + + @Override + public void setExceptionClasses(List> exceptionClasses) { + this.exceptionClasses = exceptionClasses; + } + + @Override + public String getNext() { + return next; + } + + public void setNext(String next) { + this.next = next; + } + } + + public static class LoopImpl implements Loop { + + private int parallel; + private String collection; + private String elementVariableName; + private String elementIndexName; + private String completionCondition; + + @Override + public int getParallel() { + return parallel; + } + + public void setParallel(int parallel) { + this.parallel = parallel; + } + + @Override + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + @Override + public String getElementVariableName() { + return elementVariableName; + } + + public void setElementVariableName(String elementVariableName) { + this.elementVariableName = elementVariableName; + } + + @Override + public String getElementIndexName() { + return elementIndexName; + } + + public void setElementIndexName(String elementIndexName) { + this.elementIndexName = elementIndexName; + } + + @Override + public String getCompletionCondition() { + return completionCondition; + } + + public void setCompletionCondition(String completionCondition) { + this.completionCondition = completionCondition; + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/BaseState.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/BaseState.java new file mode 100644 index 0000000..06e984b --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/BaseState.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.Map; + +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; + +/** + * BaseState + * + * @author lorne.cl + */ +public abstract class BaseState implements State { + + private transient String name; + private String type; + private String comment; + private String next; + private Map extensions; + private transient StateMachine stateMachine; + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public String getNext() { + return next; + } + + public void setNext(String next) { + this.next = next; + } + + @Override + public Map getExtensions() { + return extensions; + } + + public void setExtensions(Map extensions) { + this.extensions = extensions; + } + + @Override + public StateMachine getStateMachine() { + return stateMachine; + } + + public void setStateMachine(StateMachine stateMachine) { + this.stateMachine = stateMachine; + } + + @Override + public String getType() { + return type; + } + + protected void setType(String type) { + this.type = type; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ChoiceStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ChoiceStateImpl.java new file mode 100644 index 0000000..d8769aa --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ChoiceStateImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.List; +import java.util.Map; + +import io.seata.saga.statelang.domain.ChoiceState; +import io.seata.saga.statelang.domain.DomainConstants; + +/** + * Single selection status + * + * @author lorne.cl + */ +public class ChoiceStateImpl extends BaseState implements ChoiceState { + + private List choices; + private String defaultChoice; + /** + * key: Evaluator, value: Next + **/ + private Map choiceEvaluators; + + public ChoiceStateImpl() { + setType(DomainConstants.STATE_TYPE_CHOICE); + } + + @Override + public List getChoices() { + return choices; + } + + public void setChoices(List choices) { + this.choices = choices; + } + + @Override + public String getDefault() { + return defaultChoice; + } + + public void setDefaultChoice(String defaultChoice) { + this.defaultChoice = defaultChoice; + } + + public Map getChoiceEvaluators() { + return choiceEvaluators; + } + + public void setChoiceEvaluators(Map choiceEvaluators) { + this.choiceEvaluators = choiceEvaluators; + } + + public static class ChoiceImpl implements ChoiceState.Choice { + + private String expression; + private String next; + + @Override + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + @Override + public String getNext() { + return next; + } + + public void setNext(String next) { + this.next = next; + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImpl.java new file mode 100644 index 0000000..22a04b9 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImpl.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.CompensateSubStateMachineState; +import io.seata.saga.statelang.domain.DomainConstants; + +/** + * Used to compensate the state of the sub state machine, inherited from ServiceTaskState + * + * @author lorne.cl + */ +public class CompensateSubStateMachineStateImpl extends ServiceTaskStateImpl implements CompensateSubStateMachineState { + public CompensateSubStateMachineStateImpl() { + setType(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensationTriggerStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensationTriggerStateImpl.java new file mode 100644 index 0000000..63cdb0c --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/CompensationTriggerStateImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.CompensationTriggerState; +import io.seata.saga.statelang.domain.DomainConstants; + +/** + * Triggering the "compensation" process for the state machine + * + * @author lorne.cl + */ +public class CompensationTriggerStateImpl extends BaseState implements CompensationTriggerState { + + public CompensationTriggerStateImpl() { + setType(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/FailEndStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/FailEndStateImpl.java new file mode 100644 index 0000000..53963a1 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/FailEndStateImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.FailEndState; + +/** + * FailEndState + * + * @author lorne.cl + */ +public class FailEndStateImpl extends BaseState implements FailEndState { + + private String errorCode; + private String message; + + public FailEndStateImpl() { + setType(DomainConstants.STATE_TYPE_FAIL); + } + + @Override + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/LoopStartStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/LoopStartStateImpl.java new file mode 100644 index 0000000..e765ed5 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/LoopStartStateImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.LoopStartState; + +/** + * Start the "loop" execution for the state with loop attribute + * + * @author anselleeyy + */ +public class LoopStartStateImpl extends BaseState implements LoopStartState { + + public LoopStartStateImpl() { + setType(DomainConstants.STATE_TYPE_LOOP_START); + } + +} diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ScriptTaskStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ScriptTaskStateImpl.java new file mode 100644 index 0000000..99a395a --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ScriptTaskStateImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ScriptTaskState; + +/** + * A state used to execute script such as groovy + * + * @author lorne.cl + */ +public class ScriptTaskStateImpl extends AbstractTaskState implements ScriptTaskState { + + private static final String DEFAULT_SCRIPT_TYPE = "groovy"; + + private String scriptType = DEFAULT_SCRIPT_TYPE; + + private String scriptContent; + + public ScriptTaskStateImpl() { + setType(DomainConstants.STATE_TYPE_SCRIPT_TASK); + } + + @Override + public String getScriptType() { + return this.scriptType; + } + + @Override + public String getScriptContent() { + return this.scriptContent; + } + + public void setScriptType(String scriptType) { + this.scriptType = scriptType; + } + + public void setScriptContent(String scriptContent) { + this.scriptContent = scriptContent; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ServiceTaskStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ServiceTaskStateImpl.java new file mode 100644 index 0000000..b012487 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/ServiceTaskStateImpl.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ServiceTaskState; + +/** + * A state used to invoke a service + * + * @author lorne.cl + */ +public class ServiceTaskStateImpl extends AbstractTaskState implements ServiceTaskState { + + private String serviceType; + private String serviceName; + private String serviceMethod; + private List parameterTypes; + private Method method; + private Map statusEvaluators; + private boolean isAsync; + + public ServiceTaskStateImpl() { + setType(DomainConstants.STATE_TYPE_SERVICE_TASK); + } + + @Override + public String getServiceType() { + return serviceType; + } + + public void setServiceType(String serviceType) { + this.serviceType = serviceType; + } + + @Override + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + @Override + public String getServiceMethod() { + return serviceMethod; + } + + public void setServiceMethod(String serviceMethod) { + this.serviceMethod = serviceMethod; + } + + @Override + public List getParameterTypes() { + return parameterTypes; + } + + public void setParameterTypes(List parameterTypes) { + this.parameterTypes = parameterTypes; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + + public Map getStatusEvaluators() { + return statusEvaluators; + } + + public void setStatusEvaluators(Map statusEvaluators) { + this.statusEvaluators = statusEvaluators; + } + + public boolean isAsync() { + return isAsync; + } + + public void setAsync(boolean async) { + isAsync = async; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java new file mode 100644 index 0000000..8222619 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateInstanceImpl.java @@ -0,0 +1,310 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.Date; + +import io.seata.common.util.StringUtils; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * state execution instance + * + * @author lorne.cl + */ +public class StateInstanceImpl implements StateInstance { + + private String id; + private String machineInstanceId; + private String name; + private String type; + private String serviceName; + private String serviceMethod; + private String serviceType; + private String businessKey; + private Date gmtStarted; + private Date gmtUpdated; + private Date gmtEnd; + private boolean isForUpdate; + private Exception exception; + private Object serializedException; + private Object inputParams; + private Object serializedInputParams; + private Object outputParams; + private Object serializedOutputParams; + private ExecutionStatus status; + private String stateIdCompensatedFor; + private String stateIdRetriedFor; + private StateInstance compensationState; + private StateMachineInstance stateMachineInstance; + private boolean ignoreStatus; + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id) { + this.id = id; + } + + @Override + public String getMachineInstanceId() { + return machineInstanceId; + } + + @Override + public void setMachineInstanceId(String machineInstanceId) { + this.machineInstanceId = machineInstanceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + @Override + public String getServiceName() { + return serviceName; + } + + @Override + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + @Override + public String getServiceMethod() { + return serviceMethod; + } + + @Override + public void setServiceMethod(String serviceMethod) { + this.serviceMethod = serviceMethod; + } + + @Override + public String getServiceType() { + return serviceType; + } + + @Override + public void setServiceType(String serviceType) { + this.serviceType = serviceType; + } + + @Override + public String getBusinessKey() { + return businessKey; + } + + @Override + public void setBusinessKey(String businessKey) { + this.businessKey = businessKey; + } + + @Override + public Date getGmtStarted() { + return gmtStarted; + } + + @Override + public void setGmtStarted(Date gmtStarted) { + this.gmtStarted = gmtStarted; + } + + @Override + public Date getGmtUpdated() { + return gmtUpdated; + } + + @Override + public void setGmtUpdated(Date gmtUpdated) { + this.gmtUpdated = gmtUpdated; + } + + @Override + public Date getGmtEnd() { + return gmtEnd; + } + + @Override + public void setGmtEnd(Date gmtEnd) { + this.gmtEnd = gmtEnd; + } + + @Override + public boolean isForUpdate() { + return isForUpdate; + } + + @Override + public void setForUpdate(boolean forUpdate) { + isForUpdate = forUpdate; + } + + @Override + public String getStateIdCompensatedFor() { + return stateIdCompensatedFor; + } + + @Override + public void setStateIdCompensatedFor(String stateIdCompensatedFor) { + this.stateIdCompensatedFor = stateIdCompensatedFor; + } + + @Override + public String getStateIdRetriedFor() { + return stateIdRetriedFor; + } + + @Override + public void setStateIdRetriedFor(String stateIdRetriedFor) { + this.stateIdRetriedFor = stateIdRetriedFor; + } + + @Override + public Exception getException() { + return exception; + } + + @Override + public void setException(Exception exception) { + this.exception = exception; + } + + @Override + public Object getInputParams() { + return inputParams; + } + + @Override + public void setInputParams(Object inputParams) { + this.inputParams = inputParams; + } + + @Override + public Object getOutputParams() { + return outputParams; + } + + @Override + public void setOutputParams(Object outputParams) { + this.outputParams = outputParams; + } + + @Override + public ExecutionStatus getStatus() { + return status; + } + + @Override + public void setStatus(ExecutionStatus status) { + this.status = status; + } + + @Override + public StateInstance getCompensationState() { + return compensationState; + } + + @Override + public void setCompensationState(StateInstance compensationState) { + this.compensationState = compensationState; + } + + @Override + public StateMachineInstance getStateMachineInstance() { + return stateMachineInstance; + } + + @Override + public void setStateMachineInstance(StateMachineInstance stateMachineInstance) { + this.stateMachineInstance = stateMachineInstance; + } + + @Override + public boolean isIgnoreStatus() { + return ignoreStatus; + } + + @Override + public void setIgnoreStatus(boolean ignoreStatus) { + this.ignoreStatus = ignoreStatus; + } + + @Override + public boolean isForCompensation() { + return StringUtils.isNotBlank(this.stateIdCompensatedFor); + } + + @Override + public Object getSerializedInputParams() { + return serializedInputParams; + } + + @Override + public void setSerializedInputParams(Object serializedInputParams) { + this.serializedInputParams = serializedInputParams; + } + + @Override + public Object getSerializedOutputParams() { + return serializedOutputParams; + } + + @Override + public void setSerializedOutputParams(Object serializedOutputParams) { + this.serializedOutputParams = serializedOutputParams; + } + + @Override + public Object getSerializedException() { + return serializedException; + } + + @Override + public void setSerializedException(Object serializedException) { + this.serializedException = serializedException; + } + + @Override + public ExecutionStatus getCompensationStatus() { + if (this.compensationState != null) { + return this.compensationState.getStatus(); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java new file mode 100644 index 0000000..159e81e --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineImpl.java @@ -0,0 +1,212 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.seata.saga.statelang.domain.RecoverStrategy; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; + +/** + * state machine + * + * @author lorne.cl + */ +public class StateMachineImpl implements StateMachine { + + private String id; + private String tenantId; + private String appName = "SEATA"; + private String name; + private String comment; + private String version; + private String startState; + private Status status = Status.AC; + private RecoverStrategy recoverStrategy; + private boolean isPersist = true; + private Boolean retryPersistModeUpdate; + private Boolean compensatePersistModeUpdate; + private String type = "STATE_LANG"; + private transient String content; + private Date gmtCreate; + private Map states = new LinkedHashMap<>(); + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public String getStartState() { + return startState; + } + + @Override + public void setStartState(String startState) { + this.startState = startState; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public void setVersion(String version) { + this.version = version; + } + + @Override + public Map getStates() { + return states; + } + + public void setStates(Map states) { + this.states = states; + } + + @Override + public State getState(String name) { + return states.get(name); + } + + public void putState(String stateName, State state) { + this.states.put(stateName, state); + if (state instanceof BaseState) { + ((BaseState)state).setStateMachine(this); + } + } + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id) { + this.id = id; + } + + @Override + public String getTenantId() { + return tenantId; + } + + @Override + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + @Override + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + @Override + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + @Override + public RecoverStrategy getRecoverStrategy() { + return recoverStrategy; + } + + @Override + public void setRecoverStrategy(RecoverStrategy recoverStrategy) { + this.recoverStrategy = recoverStrategy; + } + + @Override + public String getContent() { + return content; + } + + @Override + public void setContent(String content) { + this.content = content; + } + + @Override + public boolean isPersist() { + return isPersist; + } + + public void setPersist(boolean persist) { + isPersist = persist; + } + + @Override + public Date getGmtCreate() { + return gmtCreate; + } + + @Override + public void setGmtCreate(Date gmtCreate) { + this.gmtCreate = gmtCreate; + } + + @Override + public Boolean isRetryPersistModeUpdate() { + return retryPersistModeUpdate; + } + + public void setRetryPersistModeUpdate(Boolean retryPersistModeUpdate) { + this.retryPersistModeUpdate = retryPersistModeUpdate; + } + + @Override + public Boolean isCompensatePersistModeUpdate() { + return compensatePersistModeUpdate; + } + + public void setCompensatePersistModeUpdate(Boolean compensatePersistModeUpdate) { + this.compensatePersistModeUpdate = compensatePersistModeUpdate; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java new file mode 100644 index 0000000..3b9760f --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/StateMachineInstanceImpl.java @@ -0,0 +1,277 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.StateMachineInstance; + +/** + * state machine execution instance + * + * @author lorne.cl + */ +public class StateMachineInstanceImpl implements StateMachineInstance { + + private String id; + private String machineId; + private String tenantId; + private String parentId; + private Date gmtStarted; + private String businessKey; + private Map startParams = new HashMap<>(); + private Object serializedStartParams; + private Date gmtEnd; + private Exception exception; + private Object serializedException; + private Map endParams = new HashMap<>(); + private Object serializedEndParams; + private ExecutionStatus status; + private ExecutionStatus compensationStatus; + private boolean isRunning; + private Date gmtUpdated; + private Map context; + + private StateMachine stateMachine; + private List stateList = Collections.synchronizedList(new ArrayList<>()); + private Map stateMap = new ConcurrentHashMap<>(); + + @Override + public String getId() { + return id; + } + + @Override + public void setId(String id) { + this.id = id; + } + + @Override + public String getMachineId() { + return machineId; + } + + @Override + public void setMachineId(String machineId) { + this.machineId = machineId; + } + + @Override + public String getTenantId() { + return tenantId; + } + + @Override + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + @Override + public String getParentId() { + return parentId; + } + + @Override + public void setParentId(String parentId) { + this.parentId = parentId; + } + + @Override + public Date getGmtStarted() { + return gmtStarted; + } + + @Override + public void setGmtStarted(Date gmtStarted) { + this.gmtStarted = gmtStarted; + } + + @Override + public Date getGmtEnd() { + return gmtEnd; + } + + @Override + public void setGmtEnd(Date gmtEnd) { + this.gmtEnd = gmtEnd; + } + + @Override + public void putStateInstance(String stateId, StateInstance stateInstance) { + stateInstance.setStateMachineInstance(this); + stateMap.put(stateId, stateInstance); + stateList.add(stateInstance); + } + + @Override + public ExecutionStatus getStatus() { + return status; + } + + @Override + public void setStatus(ExecutionStatus status) { + this.status = status; + } + + @Override + public ExecutionStatus getCompensationStatus() { + return compensationStatus; + } + + @Override + public void setCompensationStatus(ExecutionStatus compensationStatus) { + this.compensationStatus = compensationStatus; + } + + @Override + public boolean isRunning() { + return isRunning; + } + + @Override + public void setRunning(boolean running) { + isRunning = running; + } + + @Override + public Date getGmtUpdated() { + return gmtUpdated; + } + + @Override + public void setGmtUpdated(Date gmtUpdated) { + this.gmtUpdated = gmtUpdated; + } + + @Override + public String getBusinessKey() { + return businessKey; + } + + @Override + public void setBusinessKey(String businessKey) { + this.businessKey = businessKey; + } + + @Override + public Exception getException() { + return exception; + } + + @Override + public void setException(Exception exception) { + this.exception = exception; + } + + @Override + public Map getStartParams() { + return startParams; + } + + @Override + public void setStartParams(Map startParams) { + this.startParams = startParams; + } + + @Override + public Map getEndParams() { + return endParams; + } + + @Override + public void setEndParams(Map endParams) { + this.endParams = endParams; + } + + @Override + public Map getContext() { + return context; + } + + @Override + public void setContext(Map context) { + this.context = context; + } + + @Override + public StateMachine getStateMachine() { + return stateMachine; + } + + @Override + public void setStateMachine(StateMachine stateMachine) { + this.stateMachine = stateMachine; + } + + @Override + public List getStateList() { + return stateList; + } + + @Override + public void setStateList(List stateList) { + this.stateList = stateList; + } + + @Override + public Map getStateMap() { + return stateMap; + } + + @Override + public void setStateMap(Map stateMap) { + this.stateMap = stateMap; + } + + @Override + public Object getSerializedStartParams() { + return serializedStartParams; + } + + @Override + public void setSerializedStartParams(Object serializedStartParams) { + this.serializedStartParams = serializedStartParams; + } + + @Override + public Object getSerializedEndParams() { + return serializedEndParams; + } + + @Override + public void setSerializedEndParams(Object serializedEndParams) { + this.serializedEndParams = serializedEndParams; + } + + @Override + public Object getSerializedException() { + return serializedException; + } + + @Override + public void setSerializedException(Object serializedException) { + this.serializedException = serializedException; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SubStateMachineImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SubStateMachineImpl.java new file mode 100644 index 0000000..a61d1cb --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SubStateMachineImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.SubStateMachine; +import io.seata.saga.statelang.domain.TaskState; + +/** + * sub state machine + * + * @author lorne.cl + */ +public class SubStateMachineImpl extends ServiceTaskStateImpl implements SubStateMachine { + + private String stateMachineName; + + private TaskState compensateStateObject; + + public SubStateMachineImpl() { + setType(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE); + } + + @Override + public String getStateMachineName() { + return stateMachineName; + } + + public void setStateMachineName(String stateMachineName) { + this.stateMachineName = stateMachineName; + } + + @Override + public TaskState getCompensateStateObject() { + return compensateStateObject; + } + + public void setCompensateStateObject(TaskState compensateStateObject) { + this.compensateStateObject = compensateStateObject; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SucceedEndStateImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SucceedEndStateImpl.java new file mode 100644 index 0000000..c4533cd --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/domain/impl/SucceedEndStateImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.domain.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.SucceedEndState; + +/** + * SucceedEndState + * + * @author lorne.cl + */ +public class SucceedEndStateImpl extends BaseState implements SucceedEndState { + + public SucceedEndStateImpl() { + setType(DomainConstants.STATE_TYPE_SUCCEED); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java new file mode 100644 index 0000000..3d956d1 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParser.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +/** + * + * Json Parser + * + * @author lorne.cl + */ +public interface JsonParser { + + /** + * get Name + * + * @return + */ + String getName(); + + /** + * Object to Json string + * + * @param o + * @param prettyPrint + * @return + */ + String toJsonString(Object o, boolean prettyPrint); + + /** + * Object to Json string + * @param o + * @param ignoreAutoType + * @param prettyPrint + * @return + */ + String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint); + + /** + * parse json string to Object + * + * @param json + * @param type + * @param + * @return + */ + T parse(String json, Class type, boolean ignoreAutoType); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParserFactory.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParserFactory.java new file mode 100644 index 0000000..e70475e --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/JsonParserFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import io.seata.common.loader.EnhancedServiceLoader; +import io.seata.common.util.CollectionUtils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * JsonParserFactory + * + * @author lorne.cl + */ +public class JsonParserFactory { + + private JsonParserFactory() { + } + + private static final ConcurrentMap INSTANCES = new ConcurrentHashMap<>(); + + /** + * Gets JsonParser by name + * + * @param name parser name + * @return the JsonParser + */ + public static JsonParser getJsonParser(String name) { + return CollectionUtils.computeIfAbsent(INSTANCES, name, + key -> EnhancedServiceLoader.load(JsonParser.class, name, Thread.currentThread().getContextClassLoader())); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParser.java new file mode 100644 index 0000000..f0c00c9 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParser.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import io.seata.saga.statelang.domain.StateMachine; + +/** + * State machine parser + * + * @author lorne.cl + */ +public interface StateMachineParser { + + /** + * Parse an object (such as Json) into a State Machine model + * + * @param json + * @return + */ + StateMachine parse(String json); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParserFactory.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParserFactory.java new file mode 100644 index 0000000..9cce7f3 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateMachineParserFactory.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import io.seata.saga.statelang.parser.impl.StateMachineParserImpl; + +/** + * A simple factory of State machine language parser + * + * @author lorne.cl + */ +public class StateMachineParserFactory { + + public static StateMachineParser getStateMachineParser(String jsonParserName) { + return new StateMachineParserImpl(jsonParserName); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParser.java new file mode 100644 index 0000000..610a5c8 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParser.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import io.seata.saga.statelang.domain.State; + +/** + * State Parser + * + * @author lorne.cl + */ +public interface StateParser { + + /** + * Parse an object (such as Json) into a State model + * + * @param node + * @return + */ + T parse(Object node); +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParserFactory.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParserFactory.java new file mode 100644 index 0000000..a43fc54 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/StateParserFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.parser.impl.ChoiceStateParser; +import io.seata.saga.statelang.parser.impl.CompensateSubStateMachineStateParser; +import io.seata.saga.statelang.parser.impl.CompensationTriggerStateParser; +import io.seata.saga.statelang.parser.impl.FailEndStateParser; +import io.seata.saga.statelang.parser.impl.ScriptTaskStateParser; +import io.seata.saga.statelang.parser.impl.ServiceTaskStateParser; +import io.seata.saga.statelang.parser.impl.SubStateMachineParser; +import io.seata.saga.statelang.parser.impl.SucceedEndStateParser; + +/** + * A simple factory of state parser + * + * @author lorne.cl + */ +public class StateParserFactory { + + protected static Map stateParserMap = new ConcurrentHashMap<>(); + + static { + stateParserMap.put(DomainConstants.STATE_TYPE_SERVICE_TASK, new ServiceTaskStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_CHOICE, new ChoiceStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_COMPENSATION_TRIGGER, new CompensationTriggerStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_FAIL, new FailEndStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_SUCCEED, new SucceedEndStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_SUB_STATE_MACHINE, new SubStateMachineParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_SUB_MACHINE_COMPENSATION, + new CompensateSubStateMachineStateParser()); + stateParserMap.put(DomainConstants.STATE_TYPE_SCRIPT_TASK, new ScriptTaskStateParser()); + } + + public static StateParser getStateParser(String stateType) { + return stateParserMap.get(stateType); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/AbstractTaskStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/AbstractTaskStateParser.java new file mode 100644 index 0000000..3632ff6 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/AbstractTaskStateParser.java @@ -0,0 +1,151 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.seata.common.util.NumberUtils; +import io.seata.saga.statelang.domain.TaskState.ExceptionMatch; +import io.seata.saga.statelang.domain.TaskState.Loop; +import io.seata.saga.statelang.domain.TaskState.Retry; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import io.seata.saga.statelang.domain.impl.AbstractTaskState.ExceptionMatchImpl; +import io.seata.saga.statelang.domain.impl.AbstractTaskState.LoopImpl; +import io.seata.saga.statelang.domain.impl.AbstractTaskState.RetryImpl; + +/** + * AbstractTaskStateParser + * + * @author lorne.cl + */ +public abstract class AbstractTaskStateParser extends BaseStatePaser { + + protected void parseTaskAttributes(AbstractTaskState state, Object node) { + + parseBaseAttributes(state, node); + + Map nodeMap = (Map) node; + + state.setCompensateState((String) nodeMap.get("CompensateState")); + state.setForCompensation(Boolean.TRUE.equals(nodeMap.get("IsForCompensation"))); + state.setForUpdate(Boolean.TRUE.equals(nodeMap.get("IsForUpdate"))); + Object isPersist = nodeMap.get("IsPersist"); + if (Boolean.FALSE.equals(isPersist)) { + state.setPersist(false); + } + + // customize if update origin or append new retryStateInstLog + Object isRetryPersistModeUpdate = nodeMap.get("IsRetryPersistModeUpdate"); + if (isRetryPersistModeUpdate instanceof Boolean) { + state.setRetryPersistModeUpdate(Boolean.TRUE.equals(isRetryPersistModeUpdate)); + } + + // customize if update last or append new compensateStateInstLog + Object isCompensatePersistModeUpdate = nodeMap.get("IsCompensatePersistModeUpdate"); + if (isCompensatePersistModeUpdate instanceof Boolean) { + state.setCompensatePersistModeUpdate(Boolean.TRUE.equals(isCompensatePersistModeUpdate)); + } + + List retryList = (List) nodeMap.get("Retry"); + if (retryList != null) { + state.setRetry(parseRetry(retryList)); + } + + List catchList = (List) nodeMap.get("Catch"); + if (catchList != null) { + state.setCatches(parseCatch(catchList)); + } + + List inputList = (List) nodeMap.get("Input"); + if (inputList != null) { + state.setInput(inputList); + } + + Map outputMap = (Map) nodeMap.get("Output"); + if (outputMap != null) { + state.setOutput(outputMap); + } + + Map statusMap = (Map) nodeMap.get("Status"); + if (statusMap != null) { + state.setStatus(statusMap); + } + + Object loopObj = nodeMap.get("Loop"); + if (loopObj != null) { + state.setLoop(parseLoop(loopObj)); + } + } + + protected List parseRetry(List retryList) { + if (retryList != null) { + List retries = new ArrayList<>(retryList.size()); + for (Object retryObj : retryList) { + Map retryMap = (Map) retryObj; + RetryImpl retry = new RetryImpl(); + retry.setExceptions((List) retryMap.get("Exceptions")); + + Object intervalSeconds = retryMap.get("IntervalSeconds"); + if (intervalSeconds != null && intervalSeconds instanceof Number) { + retry.setIntervalSeconds(((Number) intervalSeconds).doubleValue()); + } + + retry.setMaxAttempts((Integer) retryMap.get("MaxAttempts")); + + Object backoffRate = retryMap.get("BackoffRate"); + if (backoffRate != null && backoffRate instanceof Number) { + retry.setBackoffRate(((Number) backoffRate).doubleValue()); + } + + retries.add(retry); + } + return retries; + } + return new ArrayList<>(0); + } + + protected List parseCatch(List catchList) { + + List exceptionMatchList = new ArrayList<>(catchList.size()); + for (Object exceptionMatchObj : catchList) { + Map exceptionMatchMap = (Map) exceptionMatchObj; + ExceptionMatchImpl exceptionMatch = new ExceptionMatchImpl(); + exceptionMatch.setExceptions((List) exceptionMatchMap.get("Exceptions")); + exceptionMatch.setNext((String) exceptionMatchMap.get("Next")); + + exceptionMatchList.add(exceptionMatch); + } + return exceptionMatchList; + } + + protected Loop parseLoop(Object loopObj) { + Map loopMap = (Map)loopObj; + LoopImpl loop = new LoopImpl(); + + Object parallel = loopMap.get("Parallel"); + loop.setParallel(NumberUtils.toInt(parallel.toString(), 1)); + + loop.setCollection((String)loopMap.get("Collection")); + loop.setElementVariableName((String)loopMap.getOrDefault("ElementVariableName", "loopElement")); + loop.setElementIndexName((String)loopMap.getOrDefault("ElementIndexName", "loopCounter")); + loop.setCompletionCondition( + (String)loopMap.getOrDefault("CompletionCondition", "[nrOfInstances] == [nrOfCompletedInstances]")); + return loop; + + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/BaseStatePaser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/BaseStatePaser.java new file mode 100644 index 0000000..648b3ca --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/BaseStatePaser.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.Map; + +import io.seata.saga.statelang.domain.impl.BaseState; + +/** + * BaseStatePaser + * + * @author lorne.cl + */ +public abstract class BaseStatePaser { + + protected void parseBaseAttributes(BaseState state, Object node) { + + Map nodeMap = (Map)node; + state.setComment((String)nodeMap.get("Comment")); + state.setNext((String)nodeMap.get("Next")); + state.setExtensions((Map)nodeMap.get("Extensions")); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ChoiceStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ChoiceStateParser.java new file mode 100644 index 0000000..1b73829 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ChoiceStateParser.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.seata.saga.statelang.domain.ChoiceState; +import io.seata.saga.statelang.domain.ChoiceState.Choice; +import io.seata.saga.statelang.domain.impl.ChoiceStateImpl; +import io.seata.saga.statelang.domain.impl.ChoiceStateImpl.ChoiceImpl; +import io.seata.saga.statelang.parser.StateParser; + +/** + * Single item selection state parser + * + * @author lorne.cl + */ +public class ChoiceStateParser extends BaseStatePaser implements StateParser { + + @Override + public ChoiceState parse(Object node) { + + ChoiceStateImpl choiceState = new ChoiceStateImpl(); + parseBaseAttributes(choiceState, node); + + Map nodeMap = (Map)node; + List choiceObjList = (List)nodeMap.get("Choices"); + List choiceStateList = new ArrayList<>(choiceObjList.size()); + for (Object choiceObj : choiceObjList) { + + Map choiceObjMap = (Map)choiceObj; + ChoiceImpl choice = new ChoiceImpl(); + choice.setExpression((String)choiceObjMap.get("Expression")); + choice.setNext((String)choiceObjMap.get("Next")); + + choiceStateList.add(choice); + } + choiceState.setChoices(choiceStateList); + + choiceState.setDefaultChoice((String)nodeMap.get("Default")); + + return choiceState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensateSubStateMachineStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensateSubStateMachineStateParser.java new file mode 100644 index 0000000..eae4af2 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensateSubStateMachineStateParser.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.ServiceTaskState; +import io.seata.saga.statelang.domain.impl.CompensateSubStateMachineStateImpl; +import io.seata.saga.statelang.parser.StateParser; +import org.springframework.util.StringUtils; + +/** + * CompensateSubStateMachineState Parser + * + * @author lorne.cl + */ +public class CompensateSubStateMachineStateParser extends AbstractTaskStateParser + implements StateParser { + + @Override + public ServiceTaskState parse(Object node) { + + CompensateSubStateMachineStateImpl compensateSubStateMachineState = new CompensateSubStateMachineStateImpl(); + compensateSubStateMachineState.setForCompensation(true); + if (node != null) { + parseTaskAttributes(compensateSubStateMachineState, node); + } + if (StringUtils.isEmpty(compensateSubStateMachineState.getName())) { + compensateSubStateMachineState.setName( + DomainConstants.COMPENSATE_SUB_MACHINE_STATE_NAME_PREFIX + compensateSubStateMachineState.hashCode()); + } + return compensateSubStateMachineState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensationTriggerStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensationTriggerStateParser.java new file mode 100644 index 0000000..1c43a27 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/CompensationTriggerStateParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import io.seata.saga.statelang.domain.CompensationTriggerState; +import io.seata.saga.statelang.domain.impl.CompensationTriggerStateImpl; +import io.seata.saga.statelang.parser.StateParser; + +/** + * 'trigger compensation process' state parser + * + * @author lorne.cl + */ +public class CompensationTriggerStateParser extends BaseStatePaser implements StateParser { + + @Override + public CompensationTriggerState parse(Object node) { + + CompensationTriggerStateImpl compensationTriggerState = new CompensationTriggerStateImpl(); + parseBaseAttributes(compensationTriggerState, node); + + return compensationTriggerState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FailEndStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FailEndStateParser.java new file mode 100644 index 0000000..94aa45a --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FailEndStateParser.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.Map; + +import io.seata.saga.statelang.domain.FailEndState; +import io.seata.saga.statelang.domain.impl.FailEndStateImpl; +import io.seata.saga.statelang.parser.StateParser; + +/** + * Failed end state parser + * + * @author lorne.cl + */ +public class FailEndStateParser extends BaseStatePaser implements StateParser { + + @Override + public FailEndState parse(Object node) { + + FailEndStateImpl failEndState = new FailEndStateImpl(); + parseBaseAttributes(failEndState, node); + + Map nodeMap = (Map)node; + failEndState.setErrorCode((String)nodeMap.get("ErrorCode")); + failEndState.setMessage((String)nodeMap.get("Message")); + + return failEndState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java new file mode 100644 index 0000000..b7ccfb3 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/FastjsonParser.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.serializer.SerializerFeature; +import io.seata.common.loader.LoadLevel; +import io.seata.saga.statelang.parser.JsonParser; + +/** + * JsonParser implement by Fastjson + * + * @author lorne.cl + */ +@LoadLevel(name = FastjsonParser.NAME) +public class FastjsonParser implements JsonParser { + + private static final SerializerFeature[] SERIALIZER_FEATURES = new SerializerFeature[] { + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.WriteClassName }; + + private static final SerializerFeature[] SERIALIZER_FEATURES_PRETTY = new SerializerFeature[] { + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.WriteClassName, + SerializerFeature.PrettyFormat }; + + private static final SerializerFeature[] FEATURES_PRETTY = new SerializerFeature[] { + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteDateUseDateFormat, + SerializerFeature.PrettyFormat }; + + public static final String NAME = "fastjson"; + + @Override + public String getName() { + return NAME; + } + + @Override + public String toJsonString(Object o, boolean prettyPrint) { + return toJsonString(o, false, prettyPrint); + } + + @Override + public String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint) { + if (prettyPrint) { + if (ignoreAutoType) { + return JSON.toJSONString(o, FEATURES_PRETTY); + } + else { + return JSON.toJSONString(o, SERIALIZER_FEATURES_PRETTY); + } + } + else { + if (ignoreAutoType) { + return JSON.toJSONString(o); + } + else { + return JSON.toJSONString(o, SERIALIZER_FEATURES); + } + } + } + + @Override + public T parse(String json, Class type, boolean ignoreAutoType) { + if (ignoreAutoType) { + return JSON.parseObject(json, type, Feature.IgnoreAutoType, Feature.OrderedField); + } + else { + return JSON.parseObject(json, type, Feature.SupportAutoType, Feature.OrderedField); + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java new file mode 100644 index 0000000..3407df8 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/JacksonJsonParser.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import io.seata.common.loader.LoadLevel; +import io.seata.saga.statelang.parser.JsonParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * JsonParser implement by Jackson + * + * @author lorne.cl + */ +@LoadLevel(name = JacksonJsonParser.NAME) +public class JacksonJsonParser implements JsonParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(JacksonJsonParser.class); + + private ObjectMapper objectMapperWithAutoType = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, "@type") + .enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER) + .setSerializationInclusion(Include.NON_NULL); + + private ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .disableDefaultTyping() + .enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER) + .setSerializationInclusion(Include.NON_NULL); + + public static final String NAME = "jackson"; + + @Override + public String getName() { + return NAME; + } + + @Override + public String toJsonString(Object o, boolean prettyPrint) { + return toJsonString(o, false, prettyPrint); + } + + @Override + public String toJsonString(Object o, boolean ignoreAutoType, boolean prettyPrint) { + try { + if (o instanceof List && ((List) o).isEmpty()) { + return "[]"; + } + if (prettyPrint) { + if (ignoreAutoType) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(o); + } + else { + return objectMapperWithAutoType.writerWithDefaultPrettyPrinter().writeValueAsString(o); + } + + } + else { + if (ignoreAutoType) { + return objectMapper.writeValueAsString(o); + } + else { + return objectMapperWithAutoType.writeValueAsString(o); + } + } + } catch (JsonProcessingException e) { + throw new RuntimeException("Parse object to json error", e); + } + } + + @Override + public T parse(String json, Class type, boolean ignoreAutoType) { + try { + if (json != null && "[]".equals(json)) { + return (T) (new ArrayList(0)); + } + if (ignoreAutoType) { + return objectMapper.readValue(json, type); + } + else { + return objectMapperWithAutoType.readValue(json, type); + } + } catch (IOException e) { + throw new RuntimeException("Parse json to object error", e); + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ScriptTaskStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ScriptTaskStateParser.java new file mode 100644 index 0000000..12e1327 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ScriptTaskStateParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import io.seata.common.util.StringUtils; +import io.seata.saga.statelang.domain.ScriptTaskState; +import io.seata.saga.statelang.domain.impl.ScriptTaskStateImpl; +import io.seata.saga.statelang.parser.StateParser; + +import java.util.Map; + +/** + * ScriptTaskState parser + * + * @author lorne.cl + */ +public class ScriptTaskStateParser extends AbstractTaskStateParser implements StateParser { + + @Override + public ScriptTaskState parse(Object node) { + + ScriptTaskStateImpl scriptTaskState = new ScriptTaskStateImpl(); + + parseTaskAttributes(scriptTaskState, node); + + Map nodeMap = (Map)node; + String scriptType = (String) nodeMap.get("ScriptType"); + if (StringUtils.isNotBlank(scriptType)) { + scriptTaskState.setScriptType(scriptType); + } + scriptTaskState.setScriptContent((String)nodeMap.get("ScriptContent")); + + scriptTaskState.setForCompensation(false); + scriptTaskState.setForUpdate(false); + scriptTaskState.setPersist(false); + + return scriptTaskState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java new file mode 100644 index 0000000..dd527e3 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.List; +import java.util.Map; + +import io.seata.saga.statelang.domain.ServiceTaskState; +import io.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import io.seata.saga.statelang.parser.StateParser; + +/** + * ServcieTaskTask parser + * + * @author lorne.cl + */ +public class ServiceTaskStateParser extends AbstractTaskStateParser implements StateParser { + + @Override + public ServiceTaskState parse(Object node) { + + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + + parseTaskAttributes(serviceTaskState, node); + + Map nodeMap = (Map)node; + serviceTaskState.setServiceName((String)nodeMap.get("ServiceName")); + serviceTaskState.setServiceMethod((String)nodeMap.get("ServiceMethod")); + serviceTaskState.setServiceType((String)nodeMap.get("ServiceType")); + serviceTaskState.setParameterTypes((List)nodeMap.get("ParameterTypes")); + Object isAsync = nodeMap.get("IsAsync"); + if (Boolean.TRUE.equals(isAsync)) { + serviceTaskState.setAsync(true); + } + + return serviceTaskState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java new file mode 100644 index 0000000..5fd73e7 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/StateMachineParserImpl.java @@ -0,0 +1,137 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.Map; +import io.seata.common.util.StringUtils; +import io.seata.saga.statelang.domain.DomainConstants; +import io.seata.saga.statelang.domain.RecoverStrategy; +import io.seata.saga.statelang.domain.State; +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.domain.impl.AbstractTaskState; +import io.seata.saga.statelang.domain.impl.BaseState; +import io.seata.saga.statelang.domain.impl.StateMachineImpl; +import io.seata.saga.statelang.parser.JsonParser; +import io.seata.saga.statelang.parser.JsonParserFactory; +import io.seata.saga.statelang.parser.StateMachineParser; +import io.seata.saga.statelang.parser.StateParser; +import io.seata.saga.statelang.parser.StateParserFactory; +import io.seata.saga.statelang.parser.utils.DesignerJsonTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * State machine language parser + * + * @author lorne.cl + */ +public class StateMachineParserImpl implements StateMachineParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineParserImpl.class); + + private String jsonParserName = DomainConstants.DEFAULT_JSON_PARSER; + + public StateMachineParserImpl(String jsonParserName) { + if (StringUtils.isNotBlank(jsonParserName)) { + this.jsonParserName = jsonParserName; + } + } + + @Override + public StateMachine parse(String json) { + + JsonParser jsonParser = JsonParserFactory.getJsonParser(jsonParserName); + if (jsonParser == null) { + throw new RuntimeException("Cannot find JsonParer by name: " + jsonParserName); + } + Map node = jsonParser.parse(json, Map.class, true); + if (DesignerJsonTransformer.isDesignerJson(node)) { + node = DesignerJsonTransformer.toStandardJson(node); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("===== Transformed standard state language:\n{}", jsonParser.toJsonString(node, true)); + } + } + + StateMachineImpl stateMachine = new StateMachineImpl(); + stateMachine.setName((String) node.get("Name")); + stateMachine.setComment((String) node.get("Comment")); + stateMachine.setVersion((String) node.get("Version")); + stateMachine.setStartState((String) node.get("StartState")); + String recoverStrategy = (String) node.get("RecoverStrategy"); + if (StringUtils.isNotBlank(recoverStrategy)) { + stateMachine.setRecoverStrategy(RecoverStrategy.valueOf(recoverStrategy)); + } + Object isPersist = node.get("IsPersist"); + if (Boolean.FALSE.equals(isPersist)) { + stateMachine.setPersist(false); + } + + // customize if update origin or append new retryStateInstLog + Object isRetryPersistModeUpdate = node.get("IsRetryPersistModeUpdate"); + if (isRetryPersistModeUpdate instanceof Boolean) { + stateMachine.setRetryPersistModeUpdate(Boolean.TRUE.equals(isRetryPersistModeUpdate)); + } + + // customize if update last or append new compensateStateInstLog + Object isCompensatePersistModeUpdate = node.get("IsCompensatePersistModeUpdate"); + if (isCompensatePersistModeUpdate instanceof Boolean) { + stateMachine.setCompensatePersistModeUpdate(Boolean.TRUE.equals(isCompensatePersistModeUpdate)); + } + + Map statesNode = (Map) node.get("States"); + statesNode.forEach((stateName, value) -> { + Map stateNode = (Map) value; + String stateType = (String) stateNode.get("Type"); + StateParser stateParser = StateParserFactory.getStateParser(stateType); + if (stateParser == null) { + throw new IllegalArgumentException("State Type [" + stateType + "] is not support"); + } + State state = stateParser.parse(stateNode); + if (state instanceof BaseState) { + ((BaseState) state).setName(stateName); + } + + if (stateMachine.getState(stateName) != null) { + throw new IllegalArgumentException("State[name:" + stateName + "] is already exists"); + } + stateMachine.putState(stateName, state); + }); + + Map stateMap = stateMachine.getStates(); + for (State state : stateMap.values()) { + if (state instanceof AbstractTaskState) { + AbstractTaskState taskState = (AbstractTaskState) state; + if (StringUtils.isNotBlank(taskState.getCompensateState())) { + taskState.setForUpdate(true); + + State compState = stateMap.get(taskState.getCompensateState()); + if (compState instanceof AbstractTaskState) { + ((AbstractTaskState) compState).setForCompensation(true); + } + } + } + } + return stateMachine; + } + + public String getJsonParserName() { + return jsonParserName; + } + + public void setJsonParserName(String jsonParserName) { + this.jsonParserName = jsonParserName; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SubStateMachineParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SubStateMachineParser.java new file mode 100644 index 0000000..f2e7822 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SubStateMachineParser.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import java.util.Map; + +import io.seata.saga.statelang.domain.ServiceTaskState; +import io.seata.saga.statelang.domain.SubStateMachine; +import io.seata.saga.statelang.domain.impl.SubStateMachineImpl; +import io.seata.saga.statelang.parser.StateParser; +import org.springframework.util.StringUtils; + +/** + * SubStateMachineParser + * + * @author lorne.cl + */ +public class SubStateMachineParser extends AbstractTaskStateParser implements StateParser { + + @Override + public SubStateMachine parse(Object node) { + + SubStateMachineImpl subStateMachine = new SubStateMachineImpl(); + + parseTaskAttributes(subStateMachine, node); + + Map nodeMap = (Map)node; + subStateMachine.setStateMachineName((String)nodeMap.get("StateMachineName")); + + if (StringUtils.isEmpty(subStateMachine.getCompensateState())) { + //build default SubStateMachine compensate state + CompensateSubStateMachineStateParser compensateSubStateMachineStateParser + = new CompensateSubStateMachineStateParser(); + ServiceTaskState subStateMachineCompenState = compensateSubStateMachineStateParser.parse(null); + subStateMachine.setCompensateStateObject(subStateMachineCompenState); + subStateMachine.setCompensateState(subStateMachineCompenState.getName()); + } + + return subStateMachine; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SucceedEndStateParser.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SucceedEndStateParser.java new file mode 100644 index 0000000..ddaac05 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/impl/SucceedEndStateParser.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.impl; + +import io.seata.saga.statelang.domain.SucceedEndState; +import io.seata.saga.statelang.domain.impl.SucceedEndStateImpl; +import io.seata.saga.statelang.parser.StateParser; + +/** + * Succeed end state parser + * + * @author lorne.cl + */ +public class SucceedEndStateParser extends BaseStatePaser implements StateParser { + + @Override + public SucceedEndState parse(Object node) { + + SucceedEndStateImpl succeedEndState = new SucceedEndStateImpl(); + parseBaseAttributes(succeedEndState, node); + + return succeedEndState; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java new file mode 100644 index 0000000..4ee3918 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java @@ -0,0 +1,284 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.utils; + +import io.seata.common.exception.FrameworkErrorCode; +import io.seata.common.exception.FrameworkException; +import io.seata.common.util.CollectionUtils; +import io.seata.saga.statelang.domain.ExecutionStatus; +import io.seata.saga.statelang.domain.StateInstance; +import io.seata.saga.statelang.domain.StateMachineInstance; +import io.seata.saga.statelang.parser.JsonParser; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Transform designer json to standard Saga State language json + * + * @author lorne.cl + */ +public class DesignerJsonTransformer { + + public static Map toStandardJson(Map designerJsonObject) { + + if (!isDesignerJson(designerJsonObject)) { + return designerJsonObject; + } + Map machineJsonObject = new LinkedHashMap<>(); + + List nodes = (List) designerJsonObject.get("nodes"); + if (CollectionUtils.isNotEmpty(nodes)) { + Map nodeMap = new LinkedHashMap<>(nodes.size()); + + for (Object node : nodes) { + Map nodeObj = (Map) node; + + transformNode(machineJsonObject, nodeMap, nodeObj); + } + + List edges = (List) designerJsonObject.get("edges"); + if (CollectionUtils.isNotEmpty(edges)) { + for (Object edge : edges) { + Map edgeObj = (Map) edge; + transformEdge(machineJsonObject, nodes, nodeMap, edgeObj); + } + } + } + return machineJsonObject; + } + + private static void transformNode(Map machineJsonObject, Map nodeMap, Map nodeObj) { + nodeMap.put((String) nodeObj.get("id"), nodeObj); + + String type = (String) nodeObj.get("stateType"); + Map propsObj = (Map) nodeObj.get("stateProps"); + if ("Start".equals(type)) { + if (propsObj != null && propsObj.containsKey("StateMachine")) { + machineJsonObject.putAll((Map) propsObj.get("StateMachine")); + } + } else if (!"Catch".equals(type)) { + Map states = (Map) CollectionUtils.computeIfAbsent(machineJsonObject, "States", + key -> new LinkedHashMap<>()); + + Map stateJsonObject = new LinkedHashMap<>(); + String stateId = (String) nodeObj.get("stateId"); + if (states.containsKey(stateId)) { + throw new RuntimeException( + "Transform designer json to standard json failed, stateId[" + stateId + "] already exists, pls rename it."); + } + + String comment = (String) nodeObj.get("label"); + if (StringUtils.hasLength(comment)) { + stateJsonObject.put("Comment", comment); + } + if (propsObj != null) { + stateJsonObject.putAll(propsObj); + } + + states.put(stateId, stateJsonObject); + + String stateType = (String) nodeObj.get("stateType"); + if ("Compensation".equals(stateType)) { + stateJsonObject.put("Type", "ServiceTask"); + } else { + stateJsonObject.put("Type", stateType); + } + } + } + + private static void transformEdge(Map machineJsonObject, List nodes, Map nodeMap, Map edgeObj) { + String sourceId = (String) edgeObj.get("source"); + String targetId = (String) edgeObj.get("target"); + if (StringUtils.hasLength(sourceId)) { + Map sourceNode = (Map) nodeMap.get(sourceId); + Map targetNode = (Map) nodeMap.get(targetId); + + if (sourceNode != null) { + Map states = (Map) machineJsonObject.get("States"); + Map sourceState = (Map) states.get((String) sourceNode.get("stateId")); + String targetStateId = (String) targetNode.get("stateId"); + + String sourceType = (String) sourceNode.get("stateType"); + if ("Start".equals(sourceType)) { + machineJsonObject.put("StartState", targetStateId); + //Make sure 'StartState' is before 'States' + machineJsonObject.put("States", machineJsonObject.remove("States")); + } else if ("ServiceTask".equals(sourceType)) { + if (targetNode != null && "Compensation".equals(targetNode.get("stateType"))) { + sourceState.put("CompensateState", targetStateId); + } else { + sourceState.put("Next", targetStateId); + } + } else if ("Catch".equals(sourceType)) { + Map catchAttachedNode = getCatchAttachedNode(sourceNode, nodes); + if (catchAttachedNode == null) { + throw new RuntimeException("'Catch' node[" + sourceNode.get("id") + "] is not attached on a 'ServiceTask' or 'ScriptTask'"); + } + Map catchAttachedState = (Map) states.get(catchAttachedNode.get("stateId")); + List catches = (List) CollectionUtils.computeIfAbsent(catchAttachedState, "Catch", + key -> new ArrayList<>()); + + Map edgeProps = (Map) edgeObj.get("stateProps"); + if (edgeProps != null) { + Map catchObj = new LinkedHashMap<>(); + catchObj.put("Exceptions", edgeProps.get("Exceptions")); + catchObj.put("Next", targetStateId); + catches.add(catchObj); + } + } else if ("Choice".equals(sourceType)) { + List choices = (List) CollectionUtils.computeIfAbsent(sourceState, "Choices", + key -> new ArrayList<>()); + + Map edgeProps = (Map) edgeObj.get("stateProps"); + if (edgeProps != null) { + if (Boolean.TRUE.equals(edgeProps.get("Default"))) { + sourceState.put("Default", targetStateId); + } else { + Map choiceObj = new LinkedHashMap<>(); + choiceObj.put("Expression", edgeProps.get("Expression")); + choiceObj.put("Next", targetStateId); + choices.add(choiceObj); + } + } + } else { + sourceState.put("Next", targetStateId); + } + } + } + } + + public static boolean isDesignerJson(Map jsonObject) { + return jsonObject != null && jsonObject.containsKey("nodes") && jsonObject.containsKey("edges"); + } + + private static Map getCatchAttachedNode(Map catchNode, List nodes) { + Number catchNodeX = (Number) catchNode.get("x"); + Number catchNodeY = (Number) catchNode.get("y"); + String catchSize = (String) catchNode.get("size"); + String[] catchSizes = catchSize.split("\\*"); + int catchWidth = Integer.parseInt(catchSizes[0]); + int catchHeight = Integer.parseInt(catchSizes[1]); + + for (Object node : nodes) { + Map nodeObj = (Map) node; + if (catchNode != nodeObj && + ("ServiceTask".equals(nodeObj.get("stateType")) + || "ScriptTask".equals(nodeObj.get("stateType")))) { + + Number nodeX = (Number) nodeObj.get("x"); + Number nodeY = (Number) nodeObj.get("y"); + + String nodeSize = (String) nodeObj.get("size"); + String[] nodeSizes = nodeSize.split("\\*"); + int nodeWidth = Integer.parseInt(nodeSizes[0]); + int nodeHeight = Integer.parseInt(nodeSizes[1]); + + if (isBordersCoincided(catchNodeX, nodeX, catchWidth, nodeWidth) + && isBordersCoincided(catchNodeY, nodeY, catchHeight, nodeHeight)) { + + return nodeObj; + } + } + } + return null; + } + + private static boolean isBordersCoincided(Number xyA, Number xyB, Number lengthA, Number lengthB) { + double centerPointLength = xyA.doubleValue() > xyB.doubleValue() ? xyA.doubleValue() - xyB.doubleValue() : xyB.doubleValue() - xyA.doubleValue(); + return ((lengthA.doubleValue() + lengthB.doubleValue()) / 2) > centerPointLength; + } + + /** + * Generate tracing graph json + * @param stateMachineInstance + * @return + */ + public static String generateTracingGraphJson(StateMachineInstance stateMachineInstance, JsonParser jsonParser) { + + if (stateMachineInstance == null) { + throw new FrameworkException("StateMachineInstance is not exits", + FrameworkErrorCode.StateMachineInstanceNotExists); + } + String stateMachineJson = stateMachineInstance.getStateMachine().getContent(); + if (StringUtils.isEmpty(stateMachineJson)) { + throw new FrameworkException("Cannot get StateMachine Json", + FrameworkErrorCode.ObjectNotExists); + } + + Map stateMachineJsonObj = jsonParser.parse(stateMachineJson, Map.class, true); + if (!DesignerJsonTransformer.isDesignerJson(stateMachineJsonObj)) { + throw new FrameworkException("StateMachine Json is not generated by Designer", + FrameworkErrorCode.InvalidConfiguration); + } + Map> stateInstanceMapGroupByName = new HashMap<>(stateMachineInstance.getStateMap().size()); + for (StateInstance stateInstance : stateMachineInstance.getStateMap().values()) { + CollectionUtils.computeIfAbsent(stateInstanceMapGroupByName, stateInstance.getName(), key -> new ArrayList<>()) + .add(stateInstance); + } + List nodesArray = (List) stateMachineJsonObj.get("nodes"); + for (Object nodeObj : nodesArray) { + Map node = (Map) nodeObj; + String stateId = (String) node.get("stateId"); + String stateType = (String) node.get("stateType"); + if ("ServiceTask".equals(stateType) + || "SubStateMachine".equals(stateType) + || "Compensation".equals(stateType)) { + node.remove("color"); + } + List stateInstanceList = stateInstanceMapGroupByName.get(stateId); + if (CollectionUtils.isNotEmpty(stateInstanceList)) { + StateInstance stateInstance = null; + if (stateInstanceList.size() == 1) { + stateInstance = stateInstanceList.get(0); + } else { + //find out latest stateInstance + for (StateInstance stateInst : stateInstanceList) { + + if (stateInstance == null + || stateInst.getGmtStarted().after(stateInstance.getGmtStarted())) { + stateInstance = stateInst; + } + } + } + node.put("stateInstanceId", stateInstance.getId()); + node.put("stateInstanceStatus", stateInstance.getStatus()); + if (ExecutionStatus.SU.equals(stateInstance.getStatus())) { + node.put("color", "green"); + Map style = new LinkedHashMap<>(); + style.put("fill", "#00D73E"); + style.put("lineWidth", 2); + node.put("style", style); + } else { + node.put("color", "red"); + Map style = new LinkedHashMap<>(); + style.put("fill", "#FF7777"); + style.put("lineWidth", 2); + node.put("style", style); + } + } + } + + if (stateMachineJsonObj != null) { + return jsonParser.toJsonString(stateMachineJsonObj, true); + } + return ""; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/IOUtils.java b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/IOUtils.java new file mode 100644 index 0000000..fda091f --- /dev/null +++ b/saga/seata-saga-statelang/src/main/java/io/seata/saga/statelang/parser/utils/IOUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +/** + * IOUtils + * copy from commons-io + * + * @author lorne.cl + */ +public class IOUtils { + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + public static String toString(InputStream input, String encoding) throws IOException { + StringWriter sw = new StringWriter(); + copy(input, sw, encoding); + return sw.toString(); + } + + public static void copy(InputStream input, Writer output, String encoding) throws IOException { + if (encoding == null) { + copy(input, output); + } else { + InputStreamReader in = new InputStreamReader(input, encoding); + copy(in, output); + } + } + + public static void copy(InputStream input, Writer output) throws IOException { + InputStreamReader in = new InputStreamReader(input); + copy(in, output); + } + + public static int copy(Reader input, Writer output) throws IOException { + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + int count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.parser.JsonParser b/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.parser.JsonParser new file mode 100644 index 0000000..84fe046 --- /dev/null +++ b/saga/seata-saga-statelang/src/main/resources/META-INF/services/io.seata.saga.statelang.parser.JsonParser @@ -0,0 +1,2 @@ +io.seata.saga.statelang.parser.impl.FastjsonParser +io.seata.saga.statelang.parser.impl.JacksonJsonParser \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java new file mode 100644 index 0000000..e343344 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/java/io/seata/saga/statelang/parser/StateParserTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2019 Seata.io Group. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.seata.saga.statelang.parser; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; + +import io.seata.saga.statelang.domain.StateMachine; +import io.seata.saga.statelang.parser.utils.DesignerJsonTransformer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.core.io.ClassPathResource; + +/** + * StateParser tests + * + * @author lorne.cl + */ +public class StateParserTests { + + @Test + public void testParser() throws IOException { + + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + StateMachine stateMachine = StateMachineParserFactory.getStateMachineParser(null).parse(json); + stateMachine.setGmtCreate(new Date()); + Assertions.assertNotNull(stateMachine); + + JsonParser jsonParser = JsonParserFactory.getJsonParser("jackson"); + String outputJson = jsonParser.toJsonString(stateMachine, true); + System.out.println(outputJson); + + + JsonParser fastjsonParser = JsonParserFactory.getJsonParser("fastjson"); + String fastjsonOutputJson = fastjsonParser.toJsonString(stateMachine, true); + System.out.println(fastjsonOutputJson); + + Assertions.assertEquals(stateMachine.getName(), "simpleTestStateMachine"); + Assertions.assertTrue(stateMachine.getStates().size() > 0); + } + + @Test + public void testDesignerJsonTransformer() throws IOException { + + ClassPathResource resource = new ClassPathResource("statelang/simple_statemachine_with_layout.json"); + String json = io.seata.saga.statelang.parser.utils.IOUtils.toString(resource.getInputStream(), "UTF-8"); + JsonParser jsonParser = JsonParserFactory.getJsonParser("jackson"); + Map parsedObj = DesignerJsonTransformer.toStandardJson(jsonParser.parse(json, Map.class, true)); + Assertions.assertNotNull(parsedObj); + + String outputJson = jsonParser.toJsonString(parsedObj, true); + System.out.println(outputJson); + + + JsonParser fastjsonParser = JsonParserFactory.getJsonParser("fastjson"); + Map fastjsonParsedObj = DesignerJsonTransformer.toStandardJson(fastjsonParser.parse(json, Map.class, true)); + Assertions.assertNotNull(fastjsonParsedObj); + + String fastjsonOutputJson = fastjsonParser.toJsonString(fastjsonParsedObj, true); + System.out.println(fastjsonOutputJson); + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/logback-test.xml b/saga/seata-saga-statelang/src/test/resources/logback-test.xml new file mode 100644 index 0000000..3e0c6d8 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + + %d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json new file mode 100644 index 0000000..417d3b5 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine.json @@ -0,0 +1,138 @@ +{ + "Name": "simpleTestStateMachine", + "Comment": "测试状态机定义", + "StartState": "FirstState", + "Version": "0.0.1", + "States": { + "FirstState": { + "Type": "ServiceTask", + "ServiceName": "is.seata.saga.DemoService", + "ServiceMethod": "foo", + "IsPersist": false, + "Next": "ScriptState" + }, + "ScriptState": { + "Type": "ScriptTask", + "ScriptType": "groovy", + "ScriptContent": "return 'hello ' + inputA", + "Input": [ + { + "inputA": "$.data1" + } + ], + "Output": { + "scriptStateResult": "$.#root" + }, + "Next": "ChoiceState" + }, + "ChoiceState": { + "Type": "Choice", + "Choices": [ + { + "Expression": "foo == 1", + "Next": "FirstMatchState" + }, + { + "Expression": "foo == 2", + "Next": "SecondMatchState" + } + ], + "Default": "FailState" + }, + "FirstMatchState": { + "Type": "ServiceTask", + "ServiceName": "is.seata.saga.DemoService", + "ServiceMethod": "bar", + "CompensateState": "CompensateFirst", + "Status": { + "return.code == 'S'": "SU", + "return.code == 'F'": "FA", + "$exception{java.lang.Throwable}": "UN" + }, + "Input": [ + { + "inputA1": "$.data1", + "inputA2": { + "a": "$.data2.a" + } + }, + { + "inputB": "$.header" + } + ], + "Output": { + "firstMatchStateResult": "$.#root" + }, + "Retry": [ + { + "Exceptions": ["java.lang.Exception"], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 1.5 + } + ], + "Catch": [ + { + "Exceptions": [ + "java.lang.Exception" + ], + "Next": "CompensationTrigger" + } + ], + "Next": "SuccessState" + }, + "CompensateFirst": { + "Type": "ServiceTask", + "ServiceName": "is.seata.saga.DemoService", + "ServiceMethod": "compensateBar", + "IsForCompensation": true, + "IsForUpdate": true, + "Input": [ + { + "input": "$.data" + } + ], + "Output": { + "firstMatchStateResult": "$.#root" + }, + "Status": { + "return.code == 'S'": "SU", + "return.code == 'F'": "FA", + "$exception{java.lang.Throwable}": "UN" + } + }, + "CompensationTrigger": { + "Type": "CompensationTrigger", + "Next": "CompensateEndState" + }, + "CompensateEndState": { + "Type": "Fail", + "ErrorCode": "StateCompensated", + "Message": "State Compensated!" + }, + "SecondMatchState": { + "Type": "SubStateMachine", + "StateMachineName": "simpleTestStateMachine", + "Input": [ + { + "input": "$.data" + }, + { + "header": "$.header" + } + ], + "Output": { + "firstMatchStateResult": "$.#root" + }, + "Next": "SuccessState" + }, + "FailState": { + "Type": "Fail", + "ErrorCode": "DefaultStateError", + "Message": "No Matches!" + }, + "SuccessState": { + "Type": "Succeed" + } + } +} \ No newline at end of file diff --git a/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_layout.json b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_layout.json new file mode 100644 index 0000000..4d886a0 --- /dev/null +++ b/saga/seata-saga-statelang/src/test/resources/statelang/simple_statemachine_with_layout.json @@ -0,0 +1,288 @@ +{ + "nodes": [ + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "#FA8C16", + "label": "Start", + "stateId": "Start", + "stateType": "Start", + "stateProps": { + "StateMachine": { + "Name": "simpleStateMachineWithCompensationAndSubMachine_layout", + "Comment": "带补偿定义和调用子状态机", + "Version": "0.0.1" + } + }, + "x": 199.875, + "y": 95, + "id": "e2d86441" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#1890FF", + "label": "FirstState", + "stateId": "FirstState", + "stateType": "ServiceTask", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "foo", + "Input": [ + { + "fooInput": "$.[a]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "x": 199.875, + "y": 213, + "id": "6111bf54" + }, + { + "type": "node", + "size": "80*72", + "shape": "flow-rhombus", + "color": "#13C2C2", + "label": "ChoiceState", + "stateId": "ChoiceState", + "stateType": "Choice", + "x": 199.875, + "y": 341.5, + "id": "5610fa37" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#1890FF", + "label": "SecondState", + "stateId": "SecondState", + "stateType": "ServiceTask", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "bar", + "Input": [ + { + "barInput": "$.[fooResult]", + "throwException": "$.[barThrowException]" + } + ], + "Output": { + "barResult": "$.#root" + }, + "Status": { + "#root != null": "SU", + "#root == null": "FA", + "$Exception{io.seata.saga.engine.exception.EngineExecutionException}": "UN" + } + }, + "x": 199.375, + "y": 468, + "id": "af5591f9" + }, + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "#05A465", + "label": "Succeed", + "stateId": "Succeed", + "stateType": "Succeed", + "x": 199.375, + "y": 609, + "id": "2fd4c8de" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-rect", + "color": "#FA8C16", + "label": "SubStateMachine", + "stateId": "CallSubStateMachine", + "stateType": "SubStateMachine", + "stateProps": { + "StateMachineName": "simpleCompensationStateMachine", + "Input": [ + { + "a": "$.1", + "barThrowException": "$.[barThrowException]", + "fooThrowException": "$.[fooThrowException]", + "compensateFooThrowException": "$.[compensateFooThrowException]" + } + ], + "Output": { + "fooResult": "$.#root" + } + }, + "x": 55.875, + "y": 467, + "id": "04ea55a5" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-capsule", + "color": "#722ED1", + "label": "CompenFirstState", + "stateId": "CompensateFirstState", + "stateType": "Compensation", + "stateProps": { + "ServiceName": "demoService", + "ServiceMethod": "compensateFoo", + "Input": [ + { + "compensateFooInput": "$.[fooResult]" + } + ] + }, + "x": 68.875, + "y": 126, + "id": "6a09a5c2" + }, + { + "type": "node", + "size": "39*39", + "shape": "flow-circle", + "color": "red", + "label": "Catch", + "stateId": "Catch", + "stateType": "Catch", + "x": 257.875, + "y": 492, + "id": "e28af1c2" + }, + { + "type": "node", + "size": "110*48", + "shape": "flow-capsule", + "color": "red", + "label": "Compensation\nTrigger", + "stateId": "CompensationTrigger", + "stateType": "CompensationTrigger", + "x": 366.875, + "y": 491.5, + "id": "e32417a0" + }, + { + "type": "node", + "size": "72*72", + "shape": "flow-circle", + "color": "red", + "label": "Fail", + "stateId": "Fail", + "stateType": "Fail", + "stateProps": { + "ErrorCode": "NOT_FOUND", + "Message": "not found" + }, + "x": 513.375, + "y": 491.5, + "id": "d21d24c9" + } + ], + "edges": [ + { + "source": "e2d86441", + "sourceAnchor": 2, + "target": "6111bf54", + "targetAnchor": 0, + "id": "51f30b96" + }, + { + "source": "6111bf54", + "sourceAnchor": 2, + "target": "5610fa37", + "targetAnchor": 0, + "id": "8c3029b1" + }, + { + "source": "5610fa37", + "sourceAnchor": 2, + "target": "af5591f9", + "targetAnchor": 0, + "id": "a9e7d5b4", + "stateProps": { + "Expression": "[a] == 1", + "Default": false + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "af5591f9", + "sourceAnchor": 2, + "target": "2fd4c8de", + "targetAnchor": 0, + "id": "61f34a49" + }, + { + "source": "6111bf54", + "sourceAnchor": 3, + "target": "6a09a5c2", + "targetAnchor": 2, + "id": "553384ab", + "style": { + "lineDash": "4" + } + }, + { + "source": "5610fa37", + "sourceAnchor": 3, + "target": "04ea55a5", + "targetAnchor": 0, + "id": "2ee91c33", + "stateProps": { + "Expression": "[a] == 2", + "Default": false + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "e28af1c2", + "sourceAnchor": 1, + "target": "e32417a0", + "targetAnchor": 3, + "id": "d854a4d0", + "stateProps": { + "Exceptions": [ + "io.seata.common.exception.FrameworkException" + ] + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "04ea55a5", + "sourceAnchor": 2, + "target": "2fd4c8de", + "targetAnchor": 3, + "id": "28734ad2" + }, + { + "source": "5610fa37", + "sourceAnchor": 1, + "target": "d21d24c9", + "targetAnchor": 0, + "id": "7c7595c0", + "stateProps": { + "Expression": "", + "Default": true + }, + "label": "", + "shape": "flow-smooth" + }, + { + "source": "e32417a0", + "sourceAnchor": 1, + "target": "d21d24c9", + "targetAnchor": 3, + "id": "16d809ce" + } + ] +} \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/.babelrc b/saga/seata-saga-statemachine-designer/.babelrc new file mode 100644 index 0000000..d9ea09a --- /dev/null +++ b/saga/seata-saga-statemachine-designer/.babelrc @@ -0,0 +1,42 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "modules": false, + "useBuiltIns": "usage", + "corejs": "core-js@3", + } + ], + "@babel/preset-react" + ], + "plugins": [ + "@babel/plugin-transform-runtime", + [ + "@babel/plugin-proposal-class-properties", + { + "loose": true + } + ], + [ + "module-resolver", + { + "alias": { + "@common": "./ggeditor/common", + "@components": "./ggeditor/components", + "@helpers": "./ggeditor/helpers", + "@utils": "./ggeditor/utils", + "@gg-editor-core": "./ggeditor/gg-editor-core" + } + } + ], + [ + "transform-inline-environment-variables", + { + "include": [ + "GG_EDITOR_VERSION" + ] + } + ] + ] +} \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/.eslintignore b/saga/seata-saga-statemachine-designer/.eslintignore new file mode 100644 index 0000000..3dda1c9 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/.eslintignore @@ -0,0 +1,4 @@ +es +cjs +dist +scripts diff --git a/saga/seata-saga-statemachine-designer/.eslintrc.json b/saga/seata-saga-statemachine-designer/.eslintrc.json new file mode 100644 index 0000000..1f6ee74 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "env": { + "browser": true + }, + "parser": "babel-eslint", + "extends": "airbnb", + "rules": { + "arrow-body-style": 0, + "class-methods-use-this": 0, + "func-names": 0, + "import/extensions": 0, + "import/no-extraneous-dependencies": 0, + "import/no-unresolved": 0, + "jsx-a11y/anchor-is-valid":0, + "jsx-a11y/no-static-element-interactions": 0, + "no-param-reassign": 0, + "no-plusplus": 0, + "object-curly-newline": 0, + "react/destructuring-assignment": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "react/no-multi-comp": 0, + "react/prefer-stateless-function": 0, + "react/prop-types": 0, + "react/sort-comp": 0 + } +} diff --git a/saga/seata-saga-statemachine-designer/.gitignore b/saga/seata-saga-statemachine-designer/.gitignore new file mode 100644 index 0000000..189994d --- /dev/null +++ b/saga/seata-saga-statemachine-designer/.gitignore @@ -0,0 +1,4 @@ +es +cjs +dist +node_modules diff --git a/saga/seata-saga-statemachine-designer/LICENSE b/saga/seata-saga-statemachine-designer/LICENSE new file mode 100644 index 0000000..0321c8b --- /dev/null +++ b/saga/seata-saga-statemachine-designer/LICENSE @@ -0,0 +1,13 @@ +Copyright 1999-2019 Seata.io Group. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/README.md b/saga/seata-saga-statemachine-designer/README.md new file mode 100644 index 0000000..e5530b5 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/README.md @@ -0,0 +1,25 @@ +English | [简体中文](README.zh-CN.md) + +# Seata Saga StateMachine Designer + +A visual graph Seata Saga StateMachine Designer based on [GGEditor](https://github.com/alibaba/GGEditor). + +## run + +```sh +$ git clone https://github.com/seata/seata.git +$ cd saga/seata-saga-statemachine-designer +$ npm install +$ npm start +``` + +## build a package +```sh +$ cd saga/saga-statemachine-designer +$ npm build +``` + +copy 'index.html' and 'dist' directory to static html directory of web server + +## Usage +To understand the state types of the state machine, please see [document of Saga](http://seata.io/zh-cn/docs/user/saga.html). After using the designer to complete the design of the state machine, you can click the 'Json View' button on the toolbar to switch to the Json view, and save the Json to the project of your own application. Although the Json generated by the designer is different from the standard Json of the Saga state machine (because the json generated by the designer has layout information), the state machine can be directly loaded and it will be converted into the Json of the Saga state machine standard. diff --git a/saga/seata-saga-statemachine-designer/README.zh-CN.md b/saga/seata-saga-statemachine-designer/README.zh-CN.md new file mode 100644 index 0000000..e2c2cf6 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/README.zh-CN.md @@ -0,0 +1,25 @@ +[English](README.md) | 简体中文 + +# Seata Saga StateMachine Designer + +Seata Saga 状态机可视化图形设计器, 基于 [GGEditor](https://github.com/alibaba/GGEditor) + +## 运行 + +```sh +$ git clone https://github.com/seata/seata.git +$ cd saga/seata-saga-statemachine-designer +$ npm install +$ npm start +``` + +## 打包 +```sh +$ cd saga/saga-statemachine-designer +$ npm build +``` + +然后将index.html和dist目录拷贝到web server的静态页面目录下 + +## 使用 +了解状态机的种状态类型,请看Saga的[文档](http://seata.io/zh-cn/docs/user/saga.html)。 通过设计器完成设计后可以点击工具栏的'Json View'按钮切换到Json视图,将Json拷贝保存到自己应用的工程里。虽然设计器生成的Json与Saga标准的Json有所差别(因为设计器生成的json带有布局信息),但状态机可以直接加载,它会将其转化成Saga状态机标准的Json。 diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/Global.js b/saga/seata-saga-statemachine-designer/ggeditor/common/Global.js new file mode 100644 index 0000000..ea2b9e2 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/Global.js @@ -0,0 +1,13 @@ +const global = { + trackable: process.env.NODE_ENV === 'production', + version: process.env.GG_EDITOR_VERSION, +}; + +export default { + get(key) { + return global[key]; + }, + set(key, value) { + global[key] = value; + }, +}; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/constants.js b/saga/seata-saga-statemachine-designer/ggeditor/common/constants.js new file mode 100644 index 0000000..6938e60 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/constants.js @@ -0,0 +1,72 @@ +export const FLOW_CONTAINER = 'J_FlowContainer'; +export const MIND_CONTAINER = 'J_MindContainer'; +export const KONI_CONTAINER = 'J_KoniContainer'; +export const TOOLBAR_CONTAINER = 'J_ToolbarContainer'; +export const MINIMAP_CONTAINER = 'J_MinimapContainer'; +export const CONTEXT_MENU_CONTAINER = 'J_ContextMenuContainer'; + +export const FLOW_CLASS_NAME = 'Flow'; +export const MIND_CLASS_NAME = 'Mind'; +export const KONI_CLASS_NAME = 'Koni'; + +export const EVENT_BEFORE_ADD_PAGE = 'beforeAddPage'; +export const EVENT_AFTER_ADD_PAGE = 'afterAddPage'; + +export const STATUS_CANVAS_SELECTED = 'canvas-selected'; +export const STATUS_NODE_SELECTED = 'node-selected'; +export const STATUS_EDGE_SELECTED = 'edge-selected'; +export const STATUS_GROUP_SELECTED = 'group-selected'; +export const STATUS_MULTI_SELECTED = 'multi-selected'; + +export const GRAPH_MOUSE_REACT_EVENTS = { + click: 'Click', + contextmenu: 'ContextMenu', + dblclick: 'DoubleClick', + drag: 'Drag', + dragend: 'DragEnd', + dragenter: 'DragEnter', + dragleave: 'DragLeave', + dragstart: 'DragStart', + drop: 'Drop', + mousedown: 'MouseDown', + mouseenter: 'MouseEnter', + mouseleave: 'MouseLeave', + mousemove: 'MouseMove', + mouseup: 'MouseUp', +}; + +export const GRAPH_OTHER_REACT_EVENTS = { + afterchange: 'onAfterChange', + afterchangesize: 'onAfterChangeSize', + afterviewportchange: 'onAfterViewportChange', + beforechange: 'onBeforeChange', + beforechangesize: 'onBeforeChangeSize', + beforeviewportchange: 'onBeforeViewportChange', + keydown: 'onKeyDown', + keyup: 'onKeyUp', + mousewheel: 'onMouseWheel', +}; + +export const PAGE_REACT_EVENTS = { + afteritemactived: 'onAfterItemActived', + afteriteminactivated: 'onAfterItemInactivated', + afteritemselected: 'onAfterItemSelected', + afteritemunactived: 'onAfterItemInactivated', + afteritemunselected: 'onAfterItemUnselected', + beforeitemactived: 'onBeforeItemActived', + beforeiteminactivated: 'onBeforeItemInactivated', + beforeitemselected: 'onBeforeItemSelected', + beforeitemunactived: 'onBeforeItemInactivated', + beforeitemunselected: 'onBeforeItemUnselected', + keyUpEditLabel: 'onKeyUpEditLabel', +}; + +export const EDITOR_REACT_EVENTS = { + aftercommandexecute: 'onAfterCommandExecute', + beforecommandexecute: 'onBeforeCommandExecute', +}; + +export const GRAPH_MOUSE_EVENTS = Object.keys(GRAPH_MOUSE_REACT_EVENTS); +export const GRAPH_OTHER_EVENTS = Object.keys(GRAPH_OTHER_REACT_EVENTS); +export const PAGE_EVENTS = Object.keys(PAGE_REACT_EVENTS); +export const EDITOR_EVENTS = Object.keys(EDITOR_REACT_EVENTS); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/index.js b/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/index.js new file mode 100644 index 0000000..166e08b --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/index.js @@ -0,0 +1,3 @@ +import React from 'react'; + +export default React.createContext({}); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/withGGEditorContext.js b/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/withGGEditorContext.js new file mode 100644 index 0000000..3bfeb75 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/context/GGEditorContext/withGGEditorContext.js @@ -0,0 +1,18 @@ +import React from 'react'; +import GGEditorContext from '@common/context/GGEditorContext'; + +export default function (WrappedComponent) { + class InjectGGEditorContext extends React.Component { + render() { + const { forwardRef, ...rest } = this.props; + + return ( + + {context => } + + ); + } + } + + return React.forwardRef((props, ref) => ); +} diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/index.js b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/index.js new file mode 100644 index 0000000..166e08b --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/index.js @@ -0,0 +1,3 @@ +import React from 'react'; + +export default React.createContext({}); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/propsAPI.js b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/propsAPI.js new file mode 100644 index 0000000..003e66d --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/propsAPI.js @@ -0,0 +1,21 @@ +class PropsAPI { + editor = null; + + constructor(editor) { + this.editor = editor; + + ['executeCommand'].forEach((key) => { + this[key] = (...params) => this.editor[key](...params); + }); + + ['read', 'save', 'add', 'find', 'update', 'remove', 'getSelected'].forEach((key) => { + this[key] = (...params) => this.currentPage[key](...params); + }); + } + + get currentPage() { + return this.editor.getCurrentPage(); + } +} + +export default PropsAPI; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/withPropsAPI.js b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/withPropsAPI.js new file mode 100644 index 0000000..5ba22b7 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/common/context/PropsAPIContext/withPropsAPI.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropsAPIContext from '@common/context/PropsAPIContext'; + +export default function (WrappedComponent) { + class InjectPropsAPI extends React.Component { + render() { + const { forwardRef, ...rest } = this.props; + + return ( + + {propsAPI => } + + ); + } + } + + return React.forwardRef((props, ref) => ); +} diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Base/Editor.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Base/Editor.js new file mode 100644 index 0000000..d84afbf --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Base/Editor.js @@ -0,0 +1,16 @@ +import GGEditorCore from '@gg-editor-core/bundle'; +import { EVENT_BEFORE_ADD_PAGE } from '@common/constants'; +import track from '@helpers/track'; +import { uniqueId } from '@utils'; + +export default class Editor extends GGEditorCore { + constructor(options) { + super(options); + + this.id = uniqueId(); + + this.on(EVENT_BEFORE_ADD_PAGE, ({ className }) => { + track({ c1: className }); + }); + } +} diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Command/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Command/index.js new file mode 100644 index 0000000..d32fc70 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Command/index.js @@ -0,0 +1,15 @@ +import React from 'react'; + +class Command extends React.Component { + render() { + const { name, children } = this.props; + + return ( +
    + {children} +
    + ); + } +} + +export default Command; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/Menu.js b/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/Menu.js new file mode 100644 index 0000000..fb77912 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/Menu.js @@ -0,0 +1,30 @@ +import React from 'react'; + +class Menu extends React.Component { + static create = function (type) { + return class TypedMenu extends Menu { + constructor(props) { + super(props, type); + } + }; + } + + constructor(props, type) { + super(props); + + this.type = type; + } + + render() { + const { children } = this.props; + const { type } = this; + + return ( +
    + {children} +
    + ); + } +} + +export default Menu; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/index.js new file mode 100644 index 0000000..21292df --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/ContextMenu/index.js @@ -0,0 +1,44 @@ +import React from 'react'; +import { pick } from '@utils'; +import Editor from '@components/Base/Editor'; +import { CONTEXT_MENU_CONTAINER } from '@common/constants'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; +import Menu from './Menu'; + +class ContextMenu extends React.Component { + contextMenu = null; + + get containerId() { + const { editor } = this.props; + + return `${CONTEXT_MENU_CONTAINER}_${editor.id}`; + } + + componentDidMount() { + const { editor } = this.props; + + this.contextMenu = new Editor.Contextmenu({ + container: this.containerId, + }); + + editor.add(this.contextMenu); + } + + render() { + const { children } = this.props; + + return ( +
    + {children} +
    + ); + } +} + +export const NodeMenu = Menu.create('node'); +export const EdgeMenu = Menu.create('edge'); +export const GroupMenu = Menu.create('group'); +export const MultiMenu = Menu.create('multi'); +export const CanvasMenu = Menu.create('canvas'); + +export default withGGEditorContext(ContextMenu); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/Panel.js b/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/Panel.js new file mode 100644 index 0000000..32bdd1e --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/Panel.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { pick } from '@utils'; + +class Panel extends React.Component { + static create = function (type) { + return class TypedPanel extends Panel { + constructor(props) { + super(props, type); + } + }; + } + + constructor(props, type) { + super(props); + + this.type = type; + } + + render() { + const { status, children } = this.props; + const { type } = this; + + if (`${type}-selected` !== status) { + return null; + } + + return ( +
    + {children} +
    + ); + } +} + +export default Panel; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/index.js new file mode 100644 index 0000000..64ccb8c --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/DetailPanel/index.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { pick } from '@utils'; +import { STATUS_CANVAS_SELECTED } from '@common/constants'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; +import Panel from './Panel'; + +class DetailPanel extends React.Component { + state = { + status: '', + } + + constructor(props) { + super(props); + + this.bindEvent(); + } + + bindEvent() { + const { onAfterAddPage } = this.props; + + onAfterAddPage(({ page }) => { + this.setState({ + status: STATUS_CANVAS_SELECTED, + }); + + page.on('statuschange', ({ status }) => { + this.setState({ status }); + }); + }); + } + + render() { + const { children } = this.props; + const { status } = this.state; + + if (!status) { + return null; + } + + return ( +
    + { + React.Children.toArray(children).map(child => React.cloneElement(child, { + status, + })) + } +
    + ); + } +} + +export const NodePanel = Panel.create('node'); +export const EdgePanel = Panel.create('edge'); +export const GroupPanel = Panel.create('group'); +export const MultiPanel = Panel.create('multi'); +export const CanvasPanel = Panel.create('canvas'); + +export default withGGEditorContext(DetailPanel); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Flow/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Flow/index.js new file mode 100644 index 0000000..6995622 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Flow/index.js @@ -0,0 +1,38 @@ +import Editor from '@components/Base/Editor'; +import { + FLOW_CONTAINER, + FLOW_CLASS_NAME, + EVENT_BEFORE_ADD_PAGE, + EVENT_AFTER_ADD_PAGE, +} from '@common/constants'; +import Page from '@components/Page'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Flow extends Page { + static defaultProps = { + data: { + nodes: [], + edges: [], + }, + }; + + get pageId() { + const { editor } = this.props; + + return `${FLOW_CONTAINER}_${editor.id}`; + } + + initPage() { + const { editor } = this.props; + + editor.emit(EVENT_BEFORE_ADD_PAGE, { className: FLOW_CLASS_NAME }); + + this.page = new Editor.Flow(this.config); + + editor.add(this.page); + + editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page }); + } +} + +export default withGGEditorContext(Flow); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/GGEditor/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/GGEditor/index.js new file mode 100644 index 0000000..13bf610 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/GGEditor/index.js @@ -0,0 +1,85 @@ +import React from 'react'; +import Editor from '@components/Base/Editor'; +import { + EDITOR_EVENTS, + EDITOR_REACT_EVENTS, + EVENT_BEFORE_ADD_PAGE, + EVENT_AFTER_ADD_PAGE, +} from '@common/constants'; +import { pick } from '@utils'; +import Global from '@common/Global'; +import GGEditorContext from '@common/context/GGEditorContext'; +import PropsAPIContext from '@common/context/PropsAPIContext'; +import PropsAPI from '@common/context/PropsAPIContext/propsAPI'; + +class GGEditor extends React.Component { + static setTrackable(value) { + Global.set('trackable', Boolean(value)); + } + + editor = null; + + get currentPage() { + return this.editor.getCurrentPage(); + } + + constructor(props) { + super(props); + + this.init(); + this.bindEvent(); + } + + addListener = (target, eventName, handler) => { + if (typeof handler === 'function') target.on(eventName, handler); + }; + + handleBeforeAddPage = (func) => { + this.editor.on(EVENT_BEFORE_ADD_PAGE, func); + }; + + handleAfterAddPage = (func) => { + const { currentPage: page } = this; + + if (page) { + func({ page }); + return; + } + + this.editor.on(EVENT_AFTER_ADD_PAGE, func); + }; + + init() { + this.editor = new Editor(); + this.ggEditor = { + editor: this.editor, + onBeforeAddPage: this.handleBeforeAddPage, + onAfterAddPage: this.handleAfterAddPage, + }; + this.propsAPI = new PropsAPI(this.editor); + } + + bindEvent() { + EDITOR_EVENTS.forEach((event) => { + this.addListener(this.editor, [event], this.props[EDITOR_REACT_EVENTS[event]]); + }); + } + + componentWillUnmount() { + this.editor.destroy(); + } + + render() { + const { children } = this.props; + + return ( + + +
    {children}
    +
    +
    + ); + } +} + +export default GGEditor; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/Item.js b/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/Item.js new file mode 100644 index 0000000..647bfee --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/Item.js @@ -0,0 +1,43 @@ +import React from 'react'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Item extends React.Component { + constructor(props) { + super(props); + + this.bindEvent(); + } + + handleMouseDown = () => { + const { type, size, shape, model } = this.props; + + if (this.page) { + this.page.beginAdd(type, { + type, + size, + shape, + ...model, + }); + } + } + + bindEvent() { + const { onAfterAddPage } = this.props; + + onAfterAddPage(({ page }) => { + this.page = page; + }); + } + + render() { + const { src, shape, children } = this.props; + + return ( +
    + {src ? {shape} : children} +
    + ); + } +} + +export default withGGEditorContext(Item); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/index.js new file mode 100644 index 0000000..6f43440 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/ItemPanel/index.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { pick } from '@utils'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; +import Item from './Item'; + +class ItemPanel extends React.Component { + page = null; + + constructor(props) { + super(props); + + this.bindEvent(); + } + + handleMouseUp = () => { + this.page.cancelAdd(); + } + + bindEvent() { + const { onAfterAddPage } = this.props; + + onAfterAddPage(({ page }) => { + this.page = page; + + document.addEventListener('mouseup', this.handleMouseUp); + }); + } + + componentWillUnmount() { + document.removeEventListener('mouseup', this.handleMouseUp); + } + + render() { + const { children } = this.props; + + return ( +
    + {children} +
    + ); + } +} + +export { Item }; + +export default withGGEditorContext(ItemPanel); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Koni/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Koni/index.js new file mode 100644 index 0000000..f8dd12b --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Koni/index.js @@ -0,0 +1,38 @@ +import Editor from '@components/Base/Editor'; +import { + KONI_CONTAINER, + KONI_CLASS_NAME, + EVENT_BEFORE_ADD_PAGE, + EVENT_AFTER_ADD_PAGE, +} from '@common/constants'; +import Page from '@components/Page'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Koni extends Page { + static defaultProps = { + data: { + nodes: [], + edges: [], + }, + }; + + get pageId() { + const { editor } = this.props; + + return `${KONI_CONTAINER}_${editor.id}`; + } + + initPage() { + const { editor } = this.props; + + editor.emit(EVENT_BEFORE_ADD_PAGE, { className: KONI_CLASS_NAME }); + + this.page = new Editor.Koni(this.config); + + editor.add(this.page); + + editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page }); + } +} + +export default withGGEditorContext(Koni); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Mind/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Mind/index.js new file mode 100644 index 0000000..b100b9c --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Mind/index.js @@ -0,0 +1,52 @@ +import Editor from '@components/Base/Editor'; +import { + MIND_CONTAINER, + MIND_CLASS_NAME, + EVENT_BEFORE_ADD_PAGE, + EVENT_AFTER_ADD_PAGE, +} from '@common/constants'; +import Page from '@components/Page'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Mind extends Page { + get pageId() { + const { editor } = this.props; + + return `${MIND_CONTAINER}_${editor.id}`; + } + + initPage() { + const { editor } = this.props; + + editor.emit(EVENT_BEFORE_ADD_PAGE, { className: MIND_CLASS_NAME }); + + this.page = new Editor.Mind(this.config); + + editor.add(this.page); + + editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page }); + } + + bindEvent() { + super.bindEvent(); + this.bindKeyUpEditLabel(); + } + + bindKeyUpEditLabel() { + const editLabel = this.page.get('labelTextArea'); + + editLabel.on('keyup', (e) => { + e.stopPropagation(); + + const item = editLabel.focusItem; + const text = editLabel.textContent; + + this.page.emit('keyUpEditLabel', { + item, + text, + }); + }); + } +} + +export default withGGEditorContext(Mind); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Minimap/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Minimap/index.js new file mode 100644 index 0000000..29eed62 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Minimap/index.js @@ -0,0 +1,89 @@ +import React from 'react'; +import G6 from '@antv/g6'; +import { pick } from '@utils'; +import { MINIMAP_CONTAINER } from '@common/constants'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +require('@antv/g6/build/plugin.tool.minimap'); + +const { Minimap: G6Minimap } = G6.Components; + +class Minimap extends React.Component { + minimap = null; + + get containerId() { + const { editor } = this.props; + + return `${MINIMAP_CONTAINER}_${editor.id}`; + } + + get currentPage() { + const { editor } = this.props; + + return editor.getCurrentPage(); + } + + constructor(props) { + super(props); + + this.bindEvent(); + } + + componentDidMount() { + this.init(); + this.bindPage(); + } + + init() { + const { + container = this.containerId, + width, + height, + viewportWindowStyle, + viewportBackStyle, + } = this.props; + + const { clientWidth, clientHeight } = document.getElementById(container); + + this.minimap = new G6Minimap({ + container, + width: width || clientWidth, + height: height || clientHeight, + viewportWindowStyle, + viewportBackStyle, + }); + + this.minimap.getGraph = () => this.currentPage.getGraph(); + } + + bindPage() { + if (!this.minimap || !this.currentPage) { + return; + } + + const graph = this.currentPage.getGraph(); + + this.minimap.bindGraph(graph); + this.minimap.debounceRender(); + } + + bindEvent() { + const { onAfterAddPage } = this.props; + + onAfterAddPage(() => { + this.bindPage(); + }); + } + + render() { + const { container } = this.props; + + if (container) { + return null; + } + + return
    ; + } +} + +export default withGGEditorContext(Minimap); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Page/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Page/index.js new file mode 100644 index 0000000..a0d403c --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Page/index.js @@ -0,0 +1,121 @@ +import React from 'react'; +import { pick, merge } from '@utils'; +import { + GRAPH_MOUSE_EVENTS, + GRAPH_OTHER_EVENTS, + PAGE_EVENTS, + GRAPH_MOUSE_REACT_EVENTS, + GRAPH_OTHER_REACT_EVENTS, + PAGE_REACT_EVENTS, +} from '@common/constants'; + +class Page extends React.Component { + page; + + get pageId() { + return ''; + } + + config = {}; + + componentDidMount() { + this.init(); + this.bindEvent(); + this.forceUpdate(); + } + + shouldComponentUpdate(props) { + const { data: newData } = props; + const { data: oldData } = this.props; + const { mode: newMode } = props.graph || {}; + const { mode: oldMode } = this.props.graph || {}; + + if (newMode !== oldMode) { + this.page.changeMode(newMode); + } + + if (newData !== oldData) { + // Remove the arrow after the connection point of the compensation type + newData.edges.forEach((item) => { + if (item.type === 'Compensation') { + item.style = { + ...item.style, + endArrow: false, + }; + } + }); + this.page.read(newData); + + return true; + } + + if (props.className !== this.props.className) return true; + + return false; + } + + get graph() { + return this.page.getGraph(); + } + + initPage() { } + + readData() { + const { data } = this.config; + + if (data) { + this.page.read(data); + } + } + + addListener = (target, eventName, handler) => { + if (typeof handler === 'function') target.on(eventName, handler); + }; + + init() { + merge(this.config, this.props, { + graph: { + container: this.pageId, + }, + }); + + this.initPage(); + this.readData(); + } + + bindEvent() { + const { addListener } = this; + + GRAPH_MOUSE_EVENTS.forEach((event) => { + const eventName = GRAPH_MOUSE_REACT_EVENTS[event]; + + addListener(this.graph, `${event}`, this.props[`on${eventName}`]); + addListener(this.graph, `node:${event}`, this.props[`onNode${eventName}`]); + addListener(this.graph, `edge:${event}`, this.props[`onEdge${eventName}`]); + addListener(this.graph, `group:${event}`, this.props[`onGroup${eventName}`]); + addListener(this.graph, `guide:${event}`, this.props[`onGuide${eventName}`]); + addListener(this.graph, `anchor:${event}`, this.props[`onAnchor${eventName}`]); + }); + + GRAPH_OTHER_EVENTS.forEach((event) => { + addListener(this.graph, [event], this.props[GRAPH_OTHER_REACT_EVENTS[event]]); + }); + + PAGE_EVENTS.forEach((event) => { + addListener(this.page, [event], this.props[PAGE_REACT_EVENTS[event]]); + }); + } + + render() { + const { page, pageId } = this; + const { children } = this.props; + + return ( +
    + {page ? children : null} +
    + ); + } +} + +export default Page; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Register/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Register/index.js new file mode 100644 index 0000000..9d44091 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Register/index.js @@ -0,0 +1,57 @@ +import React from 'react'; +import Editor from '@components/Base/Editor'; +import { upperFirst } from '@utils'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Register extends React.Component { + static create = function (type) { + class TypedRegister extends Register { + constructor(props) { + super(props, type); + } + } + + return withGGEditorContext(TypedRegister); + } + + constructor(props, type) { + super(props); + + this.type = type; + + this.bindEvent(); + } + + bindEvent() { + const { type } = this; + const { onBeforeAddPage } = this.props; + + onBeforeAddPage(({ className }) => { + let host = Editor[className]; + let keys = ['name', 'config', 'extend']; + + if (type === 'command') { + host = Editor; + } + + if (type === 'behaviour') { + keys = ['name', 'behaviour', 'dependences']; + } + + const args = keys.map(key => this.props[key]); + + host[`register${upperFirst(type)}`](...args); + }); + } + + render() { + return null; + } +} + +export const RegisterNode = Register.create('node'); +export const RegisterEdge = Register.create('edge'); +export const RegisterGroup = Register.create('group'); +export const RegisterGuide = Register.create('guide'); +export const RegisterCommand = Register.create('command'); +export const RegisterBehaviour = Register.create('behaviour'); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/components/Toolbar/index.js b/saga/seata-saga-statemachine-designer/ggeditor/components/Toolbar/index.js new file mode 100644 index 0000000..a8390af --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/components/Toolbar/index.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Editor from '@components/Base/Editor'; +import { pick } from '@utils'; +import { TOOLBAR_CONTAINER } from '@common/constants'; +import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext'; + +class Toolbar extends React.Component { + toolbar = null; + + get containerId() { + const { editor } = this.props; + + return `${TOOLBAR_CONTAINER}_${editor.id}`; + } + + constructor(props) { + super(props); + + const { editor, onAfterAddPage } = props; + + onAfterAddPage(() => { + this.toolbar = new Editor.Toolbar({ + container: this.containerId, + }); + + editor.add(this.toolbar); + }); + } + + render() { + const { children } = this.props; + + return ( +
    + {children} +
    + ); + } +} + +export default withGGEditorContext(Toolbar); diff --git a/saga/seata-saga-statemachine-designer/ggeditor/gg-editor-core/bundle.js b/saga/seata-saga-statemachine-designer/ggeditor/gg-editor-core/bundle.js new file mode 100644 index 0000000..facde7f --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/gg-editor-core/bundle.js @@ -0,0 +1,19 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.GGEditorCore=e():t.GGEditorCore=e()}(window,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=25)}([function(t,e,n){"use strict";function r(t,e,n){return t.addShape("path",{attrs:{...n,path:e}})}function i(t){return[{x:t.centerX,y:t.minY},{x:t.maxX,y:t.centerY},{x:t.centerX,y:t.maxY},{x:t.minX,y:t.centerY}]}function o(t,e){const n=i(e);let r,o,a=1/0;return n.forEach((e,n)=>{const i=(s=t,c=e,Math.sqrt(Math.pow(s.x-c.x,2)+Math.pow(s.y-c.y,2)));var s,c;ie&&(o=e,n=h,r=d,i=l)}}var a,s;return{verticalPoint:r,index:n,vertical:i}}function l(t,e,n){const r=function(t){if(!t)return{x:void 0,y:void 0};const e=t.getBBox();return{x:e.centerX,y:e.centerY}}(e);let a=t.getLinkPoints(r)[0];if(n){const r=i(t.getBBox())[n];s=t.getBBox(),c=r,u={x:e.getBBox().centerX,y:e.getBBox().centerY},((s.centerX-c.x)*(s.centerX-u.x)>0||(s.centerY-c.y)*(s.centerY-u.y)>0)&&(a=r)}var s,c,u;a=o(a,t.getBBox());let h=e.getLinkPoints(a.point)[0];return h=o(h,e.getBBox()),{sourcePoint:a,targetPoint:h}}function d(t,e,n){return c(e,n)?t.minX>e[n].x?1:t.maxXe[n].y?1:t.maxY{t.registerNode=(t,i,o)=>{r.registerNode(t,i,s(o,e+"-base",n))},t.registerEdge=(t,i,o)=>{r.registerEdge(t,i,s(o,e+"-base",n))},t.registerGroup=(t,i,o)=>{r.registerGroup(t,i,s(o,e+"-base",n))},t.registerGuide=(t,i,o)=>{r.registerGuide(t,i,s(o,e+"-base",n))},t.registerBehaviour=(t,e,n)=>{r.registerBehaviour(t,t=>{const n=t.get("page");n.set("_graph",t),e(n)},n)}},i.setRegister(i,"page"),n(44),t.exports=i},function(t,e,n){const r=n(7),i=n(22),o=n(63),a=n(64),s={...r.Util,...i,...o,...a};t.exports=s},function(t,e,n){const r=n(21),i=n(4),o=n(14),a=n(34),s=n(35),c=n(37),u=[a,n(38),n(39),s,c,n(40),n(42),n(43)];class h extends r{constructor(t){const e={defaultData:null,shortcut:{redo:!0,undo:!0,delete:!0,resetZoom:!0,autoZoom:!0,zoomIn:!0,zoomOut:!0},_controllers:{},_signals:{}};i.each(u,t=>{i.mix(e,t.CFG)}),i.mix(!0,e,t),super(e),this.isPage=!0,this.type="page",this._init()}getDelegation(t,e){if(!e){e=this.getGraph().getRootGroup()}let n;if(1!==t.length||t[0].isNode||t[0].isGroup){const r=i.getTotalBBox(t.map(t=>t.getBBox()));n=i.getRectByBox(r,e,o.nodeDelegationStyle),n.set("capture",!1)}else t[0].isEdge?n=e.addShape("path",{attrs:{path:"M0 0L 1 1",...o.edgeDelegationStyle},capture:!1}):(n=i.getRectByBox(t[0],e,o.nodeDelegationStyle),n.set("capture",!1));return n}focusGraphWrapper(){this.getGraph().getKeyboardEventWrapper().focus()}saveImage(t){const e=this.getGraph(),n=e.getBBox(),r=e.getFitViewPadding();let i,o;return e.saveImage({width:n.width+r[1]+r[3],height:n.height+r[0]+r[2],beforeTransform:()=>{i=this.getSelected().map(t=>t.id),o=this.getSelected().map(t=>t.id),this.clearSelected(),this.clearActived()},afterTransform:()=>{this.setSelected(i,!0),this.setActived(o,!0)},...t})}_init(){i.each(u,t=>{t.INIT&&this[t.INIT]()}),this.bindEvent(),this._cacheBBox()}executeCommand(t,e){const n=this.editor;n?n.executeCommand(t,e):t()}_cacheBBox(){const t=this.getGraph();this.set("bboxCache",t.getBBox())}bindEvent(){this.getGraph().on("afterchange",()=>{this._cacheBBox()})}translateLimt(t){const e=this.getGraph(),n=this.get("bboxCache"),r=e.getWidth(),o=e.getHeight(),a=[n.minX,n.minY,1],s=[n.maxX,n.maxY,1];return i.vec3.transformMat3(a,a,t),i.vec3.transformMat3(s,s,t),s[0]<100&&i.mat3.translate(t,t,[100-s[0],0]),s[1]<100&&i.mat3.translate(t,t,[0,100-s[1]]),a[1]>o-100&&i.mat3.translate(t,t,[0,o-100-a[1]]),a[0]>r-100&&i.mat3.translate(t,t,[r-100-a[0],0]),!0}setSignal(t,e){this.get("_signals")[t]=e}getSignal(t){return this.get("_signals")[t]}setController(t,e){this.get("_controllers")[t]=e}getController(t){return this.get("_controllers")[t]}destroy(){const t=this.get("_graph"),e=this.get("_controllers");i.each(e,t=>{t.destroy()}),t.destroy()}}i.each(u,t=>{i.mix(h.prototype,t.AUGMENT)}),t.exports=h},function(t,e,n){const r=n(6),i=n(14),o=r.createDOM("").getContext("2d");t.exports={...r,getPanCanvasBehaviour:t=>e=>{const n=e.getGraph();let o,a;n.behaviourOn("canvas:mouseenter",()=>{e.css({cursor:i.cursor.beforePanCanvas})}),n.behaviourOn("mouseleave",t=>{t.toShape||e.css({cursor:i.cursor.beforePanCanvas})}),n.behaviourOn("mousedown",r=>{(2!==r.button&&!t||!r.shape||r.item&&!1===r.item.dragable&&"mind-root"!==r.item.shapeObj.type&&!e.getSignal("dragEdge"))&&(o={x:r.domX,y:r.domY},e.css({cursor:i.cursor.panningCanvas}),a=n.getMatrix(),e.setCapture(!1))}),n.behaviourOn("drag",t=>{if(o){const i=t.domX-o.x,s=t.domY-o.y,c=[];r.mat3.translate(c,a,[i,s]),e.translateLimt(c)&&(n.updateMatrix(c),n.draw())}}),n.behaviourOn("mouseup",()=>{o&&(o=void 0,a=void 0,e.setCapture(!0),e.css({cursor:i.cursor.beforePanCanvas}))})},getLabelTextByTextLineWidth(t,e,n=320){o.font=e;let r=o.measureText(t).width;if(r>n){r=0;for(let e=1;e=n&&(t=t.substring(0,e)+"\n"+t.substring(e,t.length),e+=1,r=0)}return t}}},function(t,e){t.exports={orbitGap:10,nodeDefaultShape:"flow-node",edgeDefaultShape:"flow-smooth",groupDefaultShape:"flow-group",nodeActivedOutterStyle:{lineWidth:0},groupSelectedOutterStyle:{stroke:"#E0F0FF",lineWidth:2},nodeSelectedOutterStyle:{stroke:"#E0F0FF",lineWidth:2},edgeActivedStyle:{stroke:"#1890FF",strokeOpacity:.92},nodeActivedStyle:{fill:"#F3F9FF",stroke:"#1890FF"},groupActivedStyle:{stroke:"#1890FF"},edgeSelectedStyle:{lineWidth:2,strokeOpacity:.92,stroke:"#A3B1BF"},nodeSelectedStyle:{fill:"#F3F9FF",stroke:"#1890FF"},groupSelectedStyle:{stroke:"#1890FF",fillOpacity:.92},nodeStyle:{stroke:"#CED4D9",fill:"#FFFFFF",shadowOffsetX:0,shadowOffsetY:4,shadowBlur:10,shadowColor:"rgba(13, 26, 38, 0.08)",lineWidth:1,radius:4,fillOpacity:.92},edgeStyle:{stroke:"#A3B1BF",strokeOpacity:.92,lineWidth:1,lineAppendWidth:8,endArrow:!0},groupBackgroundPadding:[40,10,10,10],groupLabelOffsetX:10,groupLabelOffsetY:10,edgeLabelStyle:{fill:"#666",textAlign:"center",textBaseline:"middle"},edgeLabelRectPadding:4,edgeLabelRectStyle:{fill:"white"},nodeLabelStyle:{fill:"#666",textAlign:"center",textBaseline:"middle"},groupStyle:{stroke:"#CED4D9",radius:4},groupLabelStyle:{fill:"#666",textAlign:"left",textBaseline:"top"},multiSelectRectStyle:{fill:"#1890FF",fillOpacity:.08,stroke:"#1890FF",opacity:.1},dragNodeHoverToGroupStyle:{stroke:"#1890FF",lineWidth:2},dragNodeLeaveFromGroupStyle:{stroke:"#BAE7FF",lineWidth:2},anchorPointStyle:{radius:3.5,fill:"#fff",stroke:"#1890FF",lineAppendWidth:12},anchorHotsoptStyle:{radius:12,fill:"#1890FF",fillOpacity:.25},anchorHotsoptActivedStyle:{radius:14},anchorPointHoverStyle:{radius:4,fill:"#1890FF",fillOpacity:1,stroke:"#1890FF"},nodeControlPointStyle:{radius:4,fill:"#fff",shadowBlur:4,shadowColor:"#666"},edgeControlPointStyle:{radius:6,symbol:"square",lineAppendWidth:6,fillOpacity:0,strokeOpacity:0},nodeSelectedBoxStyle:{stroke:"#C2C2C2"},cursor:{panningCanvas:"-webkit-grabbing",beforePanCanvas:"-webkit-grab",hoverNode:"move",hoverEffectiveAnchor:"crosshair",hoverEdge:"default",hoverGroup:"move",hoverUnEffectiveAnchor:"default",hoverEdgeControllPoint:"crosshair",multiSelect:"crosshair"},zIndex:{anchorPoint:3,anchorHotsopt:2,selectedBox:1,controlPoint:4},polylineEdgeStyle:{offset:10,borderRadius:5},addToGroupDelayTime:400,outFromGroupDelayTime:400}},function(t,e,n){const r=n(7),i=n(28),o=n(29),a={whitespace:{9:"Tab",13:"Enter",32:"Space"},fn:{112:"f1 ",113:"f2 ",114:"f3 ",115:"f4 ",116:"f5 ",117:"f6 ",118:"f7 ",119:"f8 ",120:"f9 ",121:"f10",122:"f11",123:"f12"},letter:{65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z"},number:{48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9"},navigation:{37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown"},symbol:{110:"decimal point",186:"semi-colon",187:"=",188:"comma",189:"-",190:"period ",191:"/",192:"grave accent",219:"open bracket ",220:"back slash ",221:"close bracket ",222:"single quote "},smallNumberKey:{96:"numpad 0 ",97:"numpad 1 ",98:"numpad 2 ",99:"numpad 3 ",100:"numpad 4 ",101:"numpad 5 ",102:"numpad 6 ",103:"numpad 7 ",104:"numpad 8 ",105:"numpad 9 "},modifierKey:{16:"Shift",17:"Ctrl ",18:"Alt",20:"caps lock"},escKey:{8:"Backspace",46:"Delete",27:"Escape"},homeKey:{91:"Windows Key / Left command",92:"right window key ",93:"Windows Menu"},computeKey:{106:"multiply ",107:"add",109:"subtract ",111:"divide "}},s={...r.Util,...i,getNodeSize(t){if(t){if(r.Util.isNumber(t))return[t,t];if(r.Util.isString(t)){if(-1===t.indexOf("*")){const e=Number(t);return[e,e]}return t.split("*")}return t}return[96,48]},getTypeAndChar(t){let e,n;t=""+t;for(const r in a){e=r;for(const i in a[r])if(i===t)return n=a[r][i],{type:e,character:n}}return{}},getKeyboradKey:t=>t.key||s.getTypeAndChar(t.keyCode).character,getIndex:t=>t.getParent().get("children").indexOf(t),setId(t){t.id||(t.id=r.Util.guid())},pointLineDistance(t,e,n,i,o,a){const s=[n-t,i-e];if(r.Util.vec2.exactEquals(s,[0,0]))return NaN;const c=[-s[1],s[0]];r.Util.vec2.normalize(c,c);const u=[o-t,a-e];return Math.abs(r.Util.vec2.dot(u,c))},getRectByBox:(t,e,n)=>e.addShape("rect",{attrs:{...n,x:t.minX,y:t.minY,width:t.maxX-t.minX,height:t.maxY-t.minY}}),objectToValues(t){const e=[];let n;for(n in t)t.hasOwnProperty(n)&&e.push(t[n]);return e},getValue:t=>r.Util.isFunction(t)?t():t,getContrast(t,e){const n={};return r.Util.each(e,(e,r)=>{n[r]=t[r]}),n},arrowTo(t,e,n,i,o,a,s){const c=[a-i,s-o],u=r.Util.vec2.angleTo(c,[1,0],!0);return t.transform([["r",u],["t",e,n]]),t},setEndOfContenteditable(t){let e,n;document.createRange?(e=document.createRange(),e.selectNodeContents(t),e.collapse(!1),n=window.getSelection(),n.removeAllRanges(),n.addRange(e)):document.selection&&(e=document.body.createTextRange(),e.moveToElementText(t),e.collapse(!1),e.select())},matches:(t,e)=>(Element.prototype.matches||Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector||function(t){const e=(this.document||this.ownerDocument).querySelectorAll(t);let n=e.length;for(;--n>=0&&e.item(n)!==this;);return n>-1}).call(t,e),delegateEvent:(t,e,n,r)=>s.addEventListener(t,e,(function(e){let i=e.target||e.srcElement;for(;i!==t;)s.matches(i,n)&&r.call(i,Array.prototype.slice.call(arguments)),i=i.parentNode})),Palettes:o};t.exports=s},function(t,e,n){window,t.exports=function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=820)}([,function(t,e,n){"use strict";var r=function(t,e,n){t.prototype=e.prototype=n,n.constructor=t};function i(t,e){var n=Object.create(t.prototype);for(var r in e)n[r]=e[r];return n}function o(){}var a="\\s*([+-]?\\d+)\\s*",s="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",c="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",u=/^#([0-9a-f]{3})$/,h=/^#([0-9a-f]{6})$/,l=new RegExp("^rgb\\("+[a,a,a]+"\\)$"),d=new RegExp("^rgb\\("+[c,c,c]+"\\)$"),f=new RegExp("^rgba\\("+[a,a,a,s]+"\\)$"),g=new RegExp("^rgba\\("+[c,c,c,s]+"\\)$"),p=new RegExp("^hsl\\("+[s,c,c]+"\\)$"),m=new RegExp("^hsla\\("+[s,c,c,s]+"\\)$"),v={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function x(t){var e;return t=(t+"").trim().toLowerCase(),(e=u.exec(t))?new _((e=parseInt(e[1],16))>>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):(e=h.exec(t))?y(parseInt(e[1],16)):(e=l.exec(t))?new _(e[1],e[2],e[3],1):(e=d.exec(t))?new _(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=f.exec(t))?b(e[1],e[2],e[3],e[4]):(e=g.exec(t))?b(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=p.exec(t))?A(e[1],e[2]/100,e[3]/100,1):(e=m.exec(t))?A(e[1],e[2]/100,e[3]/100,e[4]):v.hasOwnProperty(t)?y(v[t]):"transparent"===t?new _(NaN,NaN,NaN,0):null}function y(t){return new _(t>>16&255,t>>8&255,255&t,1)}function b(t,e,n,r){return r<=0&&(t=e=n=NaN),new _(t,e,n,r)}function w(t){return t instanceof o||(t=x(t)),t?new _((t=t.rgb()).r,t.g,t.b,t.opacity):new _}function M(t,e,n,r){return 1===arguments.length?w(t):new _(t,e,n,null==r?1:r)}function _(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function S(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function A(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new P(t,e,n,r)}function E(t,e,n,r){return 1===arguments.length?function(t){if(t instanceof P)return new P(t.h,t.s,t.l,t.opacity);if(t instanceof o||(t=x(t)),!t)return new P;if(t instanceof P)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),s=NaN,c=a-i,u=(a+i)/2;return c?(s=e===a?(n-r)/c+6*(n0&&u<1?0:s,new P(s,c,u,t.opacity)}(t):new P(t,e,n,null==r?1:r)}function P(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function C(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}r(o,x,{displayable:function(){return this.rgb().displayable()},hex:function(){return this.rgb().hex()},toString:function(){return this.rgb()+""}}),r(_,M,i(o,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new _(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new _(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return 0<=this.r&&this.r<=255&&0<=this.g&&this.g<=255&&0<=this.b&&this.b<=255&&0<=this.opacity&&this.opacity<=1},hex:function(){return"#"+S(this.r)+S(this.g)+S(this.b)},toString:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}})),r(P,E,i(o,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new P(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new P(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new _(C(t>=240?t-240:t+120,i,r),C(t,i,r),C(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var O=Math.PI/180,I=180/Math.PI,k=.96422,B=.82521,T=6/29,N=3*T*T;function L(t){if(t instanceof G)return new G(t.l,t.a,t.b,t.opacity);if(t instanceof U){if(isNaN(t.h))return new G(t.l,0,0,t.opacity);var e=t.h*O;return new G(t.l,Math.cos(e)*t.c,Math.sin(e)*t.c,t.opacity)}t instanceof _||(t=w(t));var n,r,i=R(t.r),o=R(t.g),a=R(t.b),s=D((.2225045*i+.7168786*o+.0606169*a)/1);return i===o&&o===a?n=r=s:(n=D((.4360747*i+.3850649*o+.1430804*a)/k),r=D((.0139322*i+.0971045*o+.7141733*a)/B)),new G(116*s-16,500*(n-s),200*(s-r),t.opacity)}function Y(t,e){return new G(t,0,0,null==e?1:e)}function X(t,e,n,r){return 1===arguments.length?L(t):new G(t,e,n,null==r?1:r)}function G(t,e,n,r){this.l=+t,this.a=+e,this.b=+n,this.opacity=+r}function D(t){return t>.008856451679035631?Math.pow(t,1/3):t/N+4/29}function F(t){return t>T?t*t*t:N*(t-4/29)}function j(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function R(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function H(t){if(t instanceof U)return new U(t.h,t.c,t.l,t.opacity);if(t instanceof G||(t=L(t)),0===t.a&&0===t.b)return new U(NaN,0,t.l,t.opacity);var e=Math.atan2(t.b,t.a)*I;return new U(e<0?e+360:e,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function q(t,e,n,r){return 1===arguments.length?H(t):new U(n,e,t,null==r?1:r)}function z(t,e,n,r){return 1===arguments.length?H(t):new U(t,e,n,null==r?1:r)}function U(t,e,n,r){this.h=+t,this.c=+e,this.l=+n,this.opacity=+r}r(G,X,i(o,{brighter:function(t){return new G(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new G(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,n=isNaN(this.b)?t:t-this.b/200;return new _(j(3.1338561*(e=k*F(e))-1.6168667*(t=1*F(t))-.4906146*(n=B*F(n))),j(-.9787684*e+1.9161415*t+.033454*n),j(.0719453*e-.2289914*t+1.4052427*n),this.opacity)}})),r(U,z,i(o,{brighter:function(t){return new U(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new U(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return L(this).rgb()}}));var W=-.14861,V=1.78277,K=-.29227,Z=-.90649,Q=1.97294,$=Q*Z,J=Q*V,tt=V*K-Z*W;function et(t,e,n,r){return 1===arguments.length?function(t){if(t instanceof nt)return new nt(t.h,t.s,t.l,t.opacity);t instanceof _||(t=w(t));var e=t.r/255,n=t.g/255,r=t.b/255,i=(tt*r+$*e-J*n)/(tt+$-J),o=r-i,a=(Q*(n-i)-K*o)/Z,s=Math.sqrt(a*a+o*o)/(Q*i*(1-i)),c=s?Math.atan2(a,o)*I-120:NaN;return new nt(c<0?c+360:c,s,i,t.opacity)}(t):new nt(t,e,n,null==r?1:r)}function nt(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}r(nt,et,i(o,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new nt(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new nt(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*O,e=+this.l,n=isNaN(this.s)?0:this.s*e*(1-e),r=Math.cos(t),i=Math.sin(t);return new _(255*(e+n*(W*r+V*i)),255*(e+n*(K*r+Z*i)),255*(e+n*(Q*r)),this.opacity)}})),n.d(e,"a",(function(){return x})),n.d(e,"h",(function(){return M})),n.d(e,"e",(function(){return E})),n.d(e,"f",(function(){return X})),n.d(e,"d",(function(){return z})),n.d(e,"g",(function(){return q})),n.d(e,"c",(function(){return Y})),n.d(e,"b",(function(){return et}))},,,,,,,,,,,,,,,function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){"use strict";n.r(e);var r,i,o=0,a=0,s=0,c=0,u=0,h=0,l="object"==typeof performance&&performance.now?performance:Date,d="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function f(){return u||(d(g),u=l.now()+h)}function g(){u=0}function p(){this._call=this._time=this._next=null}function m(t,e,n){var r=new p;return r.restart(t,e,n),r}function v(){f(),++o;for(var t,e=r;e;)(t=u-e._time)>=0&&e._call.call(null,t),e=e._next;--o}function x(){u=(c=l.now())+h,o=a=0;try{v()}finally{o=0,function(){for(var t,e,n=r,o=1/0;n;)n._call?(o>n._time&&(o=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:r=e);i=t,b(o)}(),u=0}}function y(){var t=l.now(),e=t-c;e>1e3&&(h-=e,c=t)}function b(t){o||(a&&(a=clearTimeout(a)),t-u>24?(t<1/0&&(a=setTimeout(x,t-l.now()-h)),s&&(s=clearInterval(s))):(s||(c=l.now(),s=setInterval(y,1e3)),o=1,d(x)))}p.prototype=m.prototype={constructor:p,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?f():+n)+(null==e?0:+e),this._next||i===this||(i?i._next=this:r=this,i=this),this._call=t,this._time=n,b()},stop:function(){this._call&&(this._call=null,this._time=1/0,b())}};var w=function(t,e,n){var r=new p;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},M=function(t,e,n){var r=new p,i=e;return null==e?(r.restart(t,e,n),r):(e=+e,n=null==n?f():+n,r.restart((function o(a){a+=i,r.restart(o,i+=e,n),t(a)}),e,n),r)};n.d(e,"now",(function(){return f})),n.d(e,"timer",(function(){return m})),n.d(e,"timerFlush",(function(){return v})),n.d(e,"timeout",(function(){return w})),n.d(e,"interval",(function(){return M}))},,function(t,e,n){var r=n(644),i={};r.merge(i,r,{mixin:function(t,e){var n=t.CFG?"CFG":"ATTRS";if(t&&e){t._mixins=e,t[n]=t[n]||{};var r={};i.each(e,(function(e){i.augment(t,e);var o=e[n];o&&i.merge(r,o)})),t[n]=i.merge(r,t[n])}}}),t.exports=i},function(t,e,n){var r=n(117),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();t.exports=o},,,function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},,,function(t,e,n){var r={},i=n(682),o=n(748),a=n(257),s=n(727),c=n(726),u=n(725);a.mix(r,a,c,u,s,o,i),t.exports=r},,,,,,,,function(t,e,n){var r=n(38),i=n(250),o=n(249),a=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":a&&a in Object(t)?i(t):o(t)}},,,,function(t,e,n){var r=n(20).Symbol;t.exports=r},function(t,e,n){var r=n(34),i=n(23);t.exports=function(t){return"symbol"==typeof t||i(t)&&"[object Symbol]"==r(t)}},,,,,function(t,e,n){"use strict";function r(t){return+t}function i(t){return t*t}function o(t){return t*(2-t)}function a(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function s(t){return t*t*t}function c(t){return--t*t*t+1}function u(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}n.r(e);var h=function t(e){function n(t){return Math.pow(t,e)}return e=+e,n.exponent=t,n}(3),l=function t(e){function n(t){return 1-Math.pow(1-t,e)}return e=+e,n.exponent=t,n}(3),d=function t(e){function n(t){return((t*=2)<=1?Math.pow(t,e):2-Math.pow(2-t,e))/2}return e=+e,n.exponent=t,n}(3),f=Math.PI,g=f/2;function p(t){return 1-Math.cos(t*g)}function m(t){return Math.sin(t*g)}function v(t){return(1-Math.cos(f*t))/2}function x(t){return Math.pow(2,10*t-10)}function y(t){return 1-Math.pow(2,-10*t)}function b(t){return((t*=2)<=1?Math.pow(2,10*t-10):2-Math.pow(2,10-10*t))/2}function w(t){return 1-Math.sqrt(1-t*t)}function M(t){return Math.sqrt(1- --t*t)}function _(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}var S=4/11,A=7.5625;function E(t){return 1-P(1-t)}function P(t){return(t=+t)0){var o=e.strokeOpacity;r.isNil(o)||1===o||(t.globalAlpha=o),t.stroke()}this.afterPath(t)},afterPath:function(){},isHitBox:function(){return!0},isHit:function(t,e){var n=[t,e,1];if(this.invert(n),this.isHitBox()){var r=this.getBBox();if(r&&!a.box(r.minX,r.maxX,r.minY,r.maxY,n[0],n[1]))return!1}var i=this._attrs.clip;return i?(i.invert(n,this.get("canvas")),!!i.isPointInPath(n[0],n[1])&&this.isPointInPath(n[0],n[1])):this.isPointInPath(n[0],n[1])},calculateBox:function(){return null},getHitLineWidth:function(){var t=this._attrs,e=t.lineAppendWidth||0;return(t.lineWidth||0)+e},clearTotalMatrix:function(){this._cfg.totalMatrix=null,this._cfg.region=null},clearBBox:function(){this._cfg.box=null,this._cfg.region=null},getBBox:function(){var t=this._cfg.box;return t||((t=this.calculateBox())&&(t.x=t.minX,t.y=t.minY,t.width=t.maxX-t.minX,t.height=t.maxY-t.minY),this._cfg.box=t),t},clone:function(){var t=null,e=this._attrs,n={};return r.each(e,(function(t,i){c[i]&&r.isArray(e[i])?n[i]=function(t){for(var e=[],n=0;n=1?(n=1,e-1):Math.floor(n*e),o=t[r],a=t[r+1],s=r>0?t[r-1]:2*o-a,c=r180||n<-180?n-360*Math.round(n/360):n):s(isNaN(t)?e:t)}function h(t,e){var n=e-t;return n?c(t,n):s(isNaN(t)?e:t)}var l=function t(e){var n=function(t){return 1==(t=+t)?h:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):s(isNaN(e)?n:e)}}(e);function i(t,e){var i=n((t=Object(r.h)(t)).r,(e=Object(r.h)(e)).r),o=n(t.g,e.g),a=n(t.b,e.b),s=h(t.opacity,e.opacity);return function(e){return t.r=i(e),t.g=o(e),t.b=a(e),t.opacity=s(e),t+""}}return i.gamma=t,i}(1);function d(t){return function(e){var n,i,o=e.length,a=new Array(o),s=new Array(o),c=new Array(o);for(n=0;no&&(i=e.slice(o,i),s[a]?s[a]+=i:s[++a]=i),(n=n[0])===(r=r[0])?s[a]?s[a]+=r:s[++a]=r:(s[++a]=null,c.push({i:a,x:w(n,r)})),o=S.lastIndex;return o180?e+=360:e-t>180&&(t+=360),o.push({i:n.push(i(n)+"rotate(",null,r)-2,x:w(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(o.rotate,a.rotate,s,c),function(t,e,n,o){t!==e?o.push({i:n.push(i(n)+"skewX(",null,r)-2,x:w(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(o.skewX,a.skewX,s,c),function(t,e,n,r,o,a){if(t!==n||e!==r){var s=o.push(i(o)+"scale(",null,",",null,")");a.push({i:s-4,x:w(t,n)},{i:s-2,x:w(e,r)})}else 1===n&&1===r||o.push(i(o)+"scale("+n+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,s,c),o=a=null,function(t){for(var e,n=-1,r=c.length;++n1?0:i<-1?Math.PI:Math.acos(i)},e.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},e.exactEquals=function(t,e){return t[0]===e[0]&&t[1]===e[1]&&t[2]===e[2]},e.equals=function(t,e){var n=t[0],i=t[1],o=t[2],a=e[0],s=e[1],c=e[2];return Math.abs(n-a)<=r.EPSILON*Math.max(1,Math.abs(n),Math.abs(a))&&Math.abs(i-s)<=r.EPSILON*Math.max(1,Math.abs(i),Math.abs(s))&&Math.abs(o-c)<=r.EPSILON*Math.max(1,Math.abs(o),Math.abs(c))};var r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e.default=t,e}(n(59));function i(){var t=new r.ARRAY_TYPE(3);return r.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function o(t){var e=t[0],n=t[1],r=t[2];return Math.sqrt(e*e+n*n+r*r)}function a(t,e,n){var i=new r.ARRAY_TYPE(3);return i[0]=t,i[1]=e,i[2]=n,i}function s(t,e,n){return t[0]=e[0]-n[0],t[1]=e[1]-n[1],t[2]=e[2]-n[2],t}function c(t,e,n){return t[0]=e[0]*n[0],t[1]=e[1]*n[1],t[2]=e[2]*n[2],t}function u(t,e,n){return t[0]=e[0]/n[0],t[1]=e[1]/n[1],t[2]=e[2]/n[2],t}function h(t,e){var n=e[0]-t[0],r=e[1]-t[1],i=e[2]-t[2];return Math.sqrt(n*n+r*r+i*i)}function l(t,e){var n=e[0]-t[0],r=e[1]-t[1],i=e[2]-t[2];return n*n+r*r+i*i}function d(t){var e=t[0],n=t[1],r=t[2];return e*e+n*n+r*r}function f(t,e){var n=e[0],r=e[1],i=e[2],o=n*n+r*r+i*i;return o>0&&(o=1/Math.sqrt(o),t[0]=e[0]*o,t[1]=e[1]*o,t[2]=e[2]*o),t}function g(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}e.sub=s,e.mul=c,e.div=u,e.dist=h,e.sqrDist=l,e.len=o,e.sqrLen=d,e.forEach=function(){var t=i();return function(e,n,r,i,o,a){var s,c=void 0;for(n||(n=3),r||(r=0),s=i?Math.min(i*n+r,e.length):e.length,c=r;c0&&(i=1/Math.sqrt(i),t[0]=e[0]*i,t[1]=e[1]*i),t},e.dot=function(t,e){return t[0]*e[0]+t[1]*e[1]},e.cross=function(t,e,n){var r=e[0]*n[1]-e[1]*n[0];return t[0]=t[1]=0,t[2]=r,t},e.lerp=function(t,e,n,r){var i=e[0],o=e[1];return t[0]=i+r*(n[0]-i),t[1]=o+r*(n[1]-o),t},e.random=function(t,e){e=e||1;var n=2*r.RANDOM()*Math.PI;return t[0]=Math.cos(n)*e,t[1]=Math.sin(n)*e,t},e.transformMat2=function(t,e,n){var r=e[0],i=e[1];return t[0]=n[0]*r+n[2]*i,t[1]=n[1]*r+n[3]*i,t},e.transformMat2d=function(t,e,n){var r=e[0],i=e[1];return t[0]=n[0]*r+n[2]*i+n[4],t[1]=n[1]*r+n[3]*i+n[5],t},e.transformMat3=function(t,e,n){var r=e[0],i=e[1];return t[0]=n[0]*r+n[3]*i+n[6],t[1]=n[1]*r+n[4]*i+n[7],t},e.transformMat4=function(t,e,n){var r=e[0],i=e[1];return t[0]=n[0]*r+n[4]*i+n[12],t[1]=n[1]*r+n[5]*i+n[13],t},e.rotate=function(t,e,n,r){var i=e[0]-n[0],o=e[1]-n[1],a=Math.sin(r),s=Math.cos(r);return t[0]=i*s-o*a+n[0],t[1]=i*a+o*s+n[1],t},e.angle=function(t,e){var n=t[0],r=t[1],i=e[0],o=e[1],a=n*n+r*r;a>0&&(a=1/Math.sqrt(a));var s=i*i+o*o;s>0&&(s=1/Math.sqrt(s));var c=(n*i+r*o)*a*s;return c>1?0:c<-1?Math.PI:Math.acos(c)},e.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},e.exactEquals=function(t,e){return t[0]===e[0]&&t[1]===e[1]},e.equals=function(t,e){var n=t[0],i=t[1],o=e[0],a=e[1];return Math.abs(n-o)<=r.EPSILON*Math.max(1,Math.abs(n),Math.abs(o))&&Math.abs(i-a)<=r.EPSILON*Math.max(1,Math.abs(i),Math.abs(a))};var r=function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e.default=t,e}(n(59));function i(){var t=new r.ARRAY_TYPE(2);return r.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0),t}function o(t,e,n){return t[0]=e[0]-n[0],t[1]=e[1]-n[1],t}function a(t,e,n){return t[0]=e[0]*n[0],t[1]=e[1]*n[1],t}function s(t,e,n){return t[0]=e[0]/n[0],t[1]=e[1]/n[1],t}function c(t,e){var n=e[0]-t[0],r=e[1]-t[1];return Math.sqrt(n*n+r*r)}function u(t,e){var n=e[0]-t[0],r=e[1]-t[1];return n*n+r*r}function h(t){var e=t[0],n=t[1];return Math.sqrt(e*e+n*n)}function l(t){var e=t[0],n=t[1];return e*e+n*n}e.len=h,e.sub=o,e.mul=a,e.div=s,e.dist=c,e.sqrDist=u,e.sqrLen=l,e.forEach=function(){var t=i();return function(e,n,r,i,o,a){var s,c=void 0;for(n||(n=2),r||(r=0),s=i?Math.min(i*n+r,e.length):e.length,c=r;c0?1:-1};var o=function(t){for(var e=1;e1){var i=e[0].charAt(0);e.splice(1,0,e[0].substr(1)),e[0]=i}r.each(e,(function(t,n){isNaN(t)||(e[n]=+t)})),t[n]=e})),t):void 0}}},function(t,e,n){var r=n(237);t.exports=function(t){return r(t)?"":t.toString()}},function(t,e,n){var r=n(115);t.exports=function(t){return r(t,"String")}},function(t,e,n){var r=n(619),i=n(115);t.exports=function(t){if(!r(t)||!i(t,"Object"))return!1;if(null===Object.getPrototypeOf(t))return!0;for(var e=t;null!==Object.getPrototypeOf(e);)e=Object.getPrototypeOf(e);return Object.getPrototypeOf(t)===e}},function(t,e,n){var r=function(t){function e(e){var n;return(n=t.call(this)||this).options=e,n}return function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t),e.prototype.execute=function(){var t=this,e=this.options;this.roots.forEach((function(n){t.layout(n,e).eachNode((function(t){t.data.x=t.x+t.data.width/2+t.hgap,t.data.y=t.y+t.data.height/2+t.vgap}))}))},e}(n(622));t.exports=r},function(t,e,n){"undefined"!=typeof self&&self,t.exports=function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=5)}([function(t,e,n){var r=n(7);t.exports={assign:r}},function(t,e,n){var r=n(3),i=function(){function t(t,e){void 0===e&&(e={}),this.options=e,this.rootNode=r(t,e)}return t.prototype.execute=function(){throw new Error("please override this method")},t}();t.exports=i},function(t,e,n){var r=n(4),i=["LR","RL","TB","BT","H","V"],o=["LR","RL","H"],a=i[0];t.exports=function(t,e,n){var s=e.direction||a;if(e.isHorizontal=function(t){return o.indexOf(t)>-1}(s),s&&-1===i.indexOf(s))throw new TypeError("Invalid direction: "+s);if(s===i[0])n(t,e);else if(s===i[1])n(t,e),t.right2left();else if(s===i[2])n(t,e);else if(s===i[3])n(t,e),t.bottom2top();else if(s===i[4]||s===i[5]){var c=r(t,e),u=c.left,h=c.right;n(u,e),n(h,e),e.isHorizontal?u.right2left():u.bottom2top(),h.translate(u.x-h.x,u.y-h.y),t.x=u.x,t.y=h.y;var l=t.getBoundingBox();e.isHorizontal?l.top<0&&t.translate(0,-l.top):l.left<0&&t.translate(-l.left,0)}return t.translate(-(t.x+t.width/2+t.hgap),-(t.y+t.height/2+t.vgap)),t}},function(t,e,n){var r=n(0),i={getId:function(t){return t.id||t.name},getHGap:function(t){return t.hgap||18},getVGap:function(t){return t.vgap||18},getChildren:function(t){return t.children},getHeight:function(t){return t.height||36},getWidth:function(t){var e=t.name||" ";return t.width||18*e.split("").length}};function o(t,e){var n=this;if(n.vgap=n.hgap=0,t instanceof o)return t;n.data=t;var r=e.getHGap(t),i=e.getVGap(t);return n.width=e.getWidth(t),n.height=e.getHeight(t),n.id=e.getId(t),n.x=n.y=0,n.depth=0,n.children||(n.children=[]),n.addGap(r,i),n}r.assign(o.prototype,{isRoot:function(){return 0===this.depth},isLeaf:function(){return 0===this.children.length},addGap:function(t,e){this.hgap+=t,this.vgap+=e,this.width+=2*t,this.height+=2*e},eachNode:function(t){for(var e,n=[this];e=n.pop();)t(e),n=n.concat(e.children)},DFTraverse:function(t){this.eachNode(t)},BFTraverse:function(t){for(var e,n=[this];e=n.shift();)t(e),n=n.concat(e.children)},getBoundingBox:function(){var t={left:Number.MAX_VALUE,top:Number.MAX_VALUE,width:0,height:0};return this.eachNode((function(e){t.left=Math.min(t.left,e.x),t.top=Math.min(t.top,e.y),t.width=Math.max(t.width,e.x+e.width),t.height=Math.max(t.height,e.y+e.height)})),t},translate:function(t,e){void 0===t&&(t=0),void 0===e&&(e=0),this.eachNode((function(n){n.x+=t,n.y+=e}))},right2left:function(){var t=this.getBoundingBox();this.eachNode((function(e){e.x=e.x-2*(e.x-t.left)-e.width})),this.translate(t.width,0)},bottom2top:function(){var t=this.getBoundingBox();this.eachNode((function(e){e.y=e.y-2*(e.y-t.top)-e.height})),this.translate(0,t.height)}}),t.exports=function(t,e,n){void 0===e&&(e={});var a,s=new o(t,e=r.assign({},i,e)),c=[s];if(!n&&!t.collapsed)for(;a=c.pop();)if(!a.data.collapsed){var u=e.getChildren(a.data),h=u?u.length:0;if(a.children=new Array(h),u&&h)for(var l=0;ln.low&&(n=n.nxt);var l=i+r.prelim+r.w-(h+o.prelim);l>0&&(h+=l,a(t,e,n.index,l));var d=u(r),f=u(o);d<=f&&null!==(r=c(r))&&(i+=r.mod),d>=f&&null!==(o=s(o))&&(h+=o.mod)}!r&&o?function(t,e,n,r){var i=t.c[0].el;i.tl=n;var o=r-n.mod-t.c[0].msel;i.mod+=o,i.prelim-=o,t.c[0].el=t.c[e].el,t.c[0].msel=t.c[e].msel}(t,e,o,h):r&&!o&&function(t,e,n,r){var i=t.c[e].er;i.tr=n;var o=r-n.mod-t.c[e].mser;i.mod+=o,i.prelim-=o,t.c[e].er=t.c[e-1].er,t.c[e].mser=t.c[e-1].mser}(t,e,r,i)}function a(t,e,n,r){t.c[e].mod+=r,t.c[e].msel+=r,t.c[e].mser+=r,function(t,e,n,r){if(n!==e-1){var i=e-n;t.c[n+1].shift+=r/i,t.c[e].shift-=r/i,t.c[e].change-=r-r/i}}(t,e,n,r)}function s(t){return 0===t.cs?t.tl:t.c[0]}function c(t){return 0===t.cs?t.tr:t.c[t.cs-1]}function u(t){return t.y+t.h}function h(t,e,n){for(;null!==n&&t>=n.low;)n=n.nxt;return{low:t,index:e,nxt:n}}!function t(e,n,r){void 0===r&&(r=0),n?(e.x=r,r+=e.width):(e.y=r,r+=e.height),e.children.forEach((function(e){t(e,n,r)}))}(t,r);var l=n.fromNode(t,r);return function t(e){if(0!==e.cs){t(e.c[0]);for(var n=h(u(e.c[0].el),0,null),r=1;ro&&(o=e.depth);var n=e.children,r=n.length,i=new function(t,e){void 0===t&&(t=0),void 0===e&&(e=[]);var n=this;n.x=n.y=0,n.leftChild=n.rightChild=null,n.height=0,n.children=e}(e.height,[]);return n.forEach((function(e,n){var o=t(e);i.children.push(o),0===n&&(i.leftChild=o),n===r-1&&(i.rightChild=o)})),i.originNode=e,i.isLeaf=e.isLeaf(),i}(t);return function t(e){if(e.isLeaf||0===e.children.length)e.drawingDepth=o;else{var n=e.children.map((function(e){return t(e)})),r=Math.min.apply(null,n);e.drawingDepth=r-1}return e.drawingDepth}(a),function t(r){r.x=r.drawingDepth*e.rankSep,r.isLeaf?(r.y=0,n&&(r.y=n.y+n.height+e.nodeSep,r.originNode.parent!==n.originNode.parent&&(r.y+=e.subTreeSep)),n=r):(r.children.forEach((function(e){t(e)})),r.y=(r.leftChild.y+r.rightChild.y)/2)}(a),function t(e,n,r){r?(n.x=e.x,n.y=e.y):(n.x=e.y,n.y=e.x),e.children.forEach((function(e,i){t(e,n.children[i],r)}))}(a,t,e.isHorizontal),t}},function(t,e,n){var r=n(1),i=n(12),o=n(4),a=n(0),s=["LR","RL","H"],c=s[0],u=function(t){function e(){return t.apply(this,arguments)||this}return function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t),e.prototype.execute=function(){var t=this.options,e=this.rootNode;t.isHorizontal=!0;var n=t.indent,r=t.direction||c;if(r&&-1===s.indexOf(r))throw new TypeError("Invalid direction: "+r);if(r===s[0])i(e,n);else if(r===s[1])i(e,n),e.right2left();else if(r===s[2]){var a=o(e,t),u=a.left,h=a.right;i(u,n),u.right2left(),i(h,n);var l=u.getBoundingBox();h.translate(l.width,0),e.x=h.x-e.width/2}return e},e}(r),h={};t.exports=function(t,e){return e=a.assign({},h,e),new u(t,e).execute()}},function(t,e){t.exports=function(t,e){void 0===e&&(e=20);var n=null;t.eachNode((function(t){!function(t,e,n){t.x+=n*t.depth,t.y=e?e.y+e.height:0}(t,n,e),n=t}))}},function(t,e,n){var r=n(1),i=n(14),o=n(2),a=n(0),s=function(t){function e(){return t.apply(this,arguments)||this}return function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t),e.prototype.execute=function(){return o(this.rootNode,this.options,i)},e}(r),c={};t.exports=function(t,e){return e=a.assign({},c,e),new s(t,e).execute()}},function(t,e,n){var r=n(0),i={getSubTreeSep:function(){return 0}};t.exports=function(t,e){void 0===e&&(e={}),e=r.assign({},i,e),t.parent={x:0,width:0,height:0,y:0},t.BFTraverse((function(t){t.x=t.parent.x+t.parent.width})),t.parent=null,function t(e,n){var r=0;return e.children.length?e.children.forEach((function(e){r+=t(e,n)})):r=e.height,e._subTreeSep=n.getSubTreeSep(e.data),e.totalHeight=Math.max(e.height,r)+2*e._subTreeSep,e.totalHeight}(t,e),t.startY=0,t.y=t.totalHeight/2-t.height/2,t.eachNode((function(t){var e=t.children,n=e.length;if(n){var r=e[0];if(r.startY=t.startY+t._subTreeSep,1===n)r.y=t.y+t.height/2-r.height/2;else{r.y=r.startY+r.totalHeight/2-r.height/2;for(var i=1;ie.height)e.y=i.y+a/2-e.height/2;else if(1!==n.length||e.height>s){var c=e.y+(e.height-a)/2-i.y;n.forEach((function(t){t.translate(0,c)}))}else e.y=(i.y+i.height/2+o.y+o.height/2)/2-e.height/2}}(t)}}])},function(t,e,n){var r=n(26),i=function(){var t=e.prototype;function e(t){var e=this.getDefaultCfg();r.mix(this,e,t),this._init()}return t.getDefaultCfg=function(){return{}},t._init=function(){},t.destroy=function(){},e}();t.exports=i},function(t,e,n){var r=n(19),i=n(613),o=n(612),a=n(640),s=n(639),c=r.vec3,u=r.mat3,h=["m","l","c","a","q","h","v","t","s","z"];function l(t,e,n){return{x:n.x+t,y:n.y+e}}function d(t,e){return{x:e.x+(e.x-t.x),y:e.y+(e.y-t.y)}}function f(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])}function g(t,e){return(t[0]*e[0]+t[1]*e[1])/(f(t)*f(e))}function p(t,e){return(t[0]*e[1]=0,u=c?n.toUpperCase():n,f=t,m=e.endPoint,v=f[1],x=f[2];switch(u){default:break;case"M":s=c?l(v,x,m):{x:v,y:x},this.command="M",this.params=[m,s],this.subStart=s,this.endPoint=s;break;case"L":s=c?l(v,x,m):{x:v,y:x},this.command="L",this.params=[m,s],this.subStart=e.subStart,this.endPoint=s,this.endTangent=function(){return[s.x-m.x,s.y-m.y]},this.startTangent=function(){return[m.x-s.x,m.y-s.y]};break;case"H":s=c?l(v,0,m):{x:v,y:m.y},this.command="L",this.params=[m,s],this.subStart=e.subStart,this.endPoint=s,this.endTangent=function(){return[s.x-m.x,s.y-m.y]},this.startTangent=function(){return[m.x-s.x,m.y-s.y]};break;case"V":s=c?l(0,v,m):{x:m.x,y:v},this.command="L",this.params=[m,s],this.subStart=e.subStart,this.endPoint=s,this.endTangent=function(){return[s.x-m.x,s.y-m.y]},this.startTangent=function(){return[m.x-s.x,m.y-s.y]};break;case"Q":c?(i=l(v,x,m),o=l(f[3],f[4],m)):(i={x:v,y:x},o={x:f[3],y:f[4]}),this.command="Q",this.params=[m,i,o],this.subStart=e.subStart,this.endPoint=o,this.endTangent=function(){return[o.x-i.x,o.y-i.y]},this.startTangent=function(){return[m.x-i.x,m.y-i.y]};break;case"T":o=c?l(v,x,m):{x:v,y:x},"Q"===e.command?(i=d(e.params[1],m),this.command="Q",this.params=[m,i,o],this.subStart=e.subStart,this.endPoint=o,this.endTangent=function(){return[o.x-i.x,o.y-i.y]},this.startTangent=function(){return[m.x-i.x,m.y-i.y]}):(this.command="TL",this.params=[m,o],this.subStart=e.subStart,this.endPoint=o,this.endTangent=function(){return[o.x-m.x,o.y-m.y]},this.startTangent=function(){return[m.x-o.x,m.y-o.y]});break;case"C":c?(i=l(v,x,m),o=l(f[3],f[4],m),a=l(f[5],f[6],m)):(i={x:v,y:x},o={x:f[3],y:f[4]},a={x:f[5],y:f[6]}),this.command="C",this.params=[m,i,o,a],this.subStart=e.subStart,this.endPoint=a,this.endTangent=function(){return[a.x-o.x,a.y-o.y]},this.startTangent=function(){return[m.x-i.x,m.y-i.y]};break;case"S":c?(o=l(v,x,m),a=l(f[3],f[4],m)):(o={x:v,y:x},a={x:f[3],y:f[4]}),"C"===e.command?(i=d(e.params[2],m),this.command="C",this.params=[m,i,o,a],this.subStart=e.subStart,this.endPoint=a,this.endTangent=function(){return[a.x-o.x,a.y-o.y]},this.startTangent=function(){return[m.x-i.x,m.y-i.y]}):(this.command="SQ",this.params=[m,o,a],this.subStart=e.subStart,this.endPoint=a,this.endTangent=function(){return[a.x-o.x,a.y-o.y]},this.startTangent=function(){return[m.x-o.x,m.y-o.y]});break;case"A":var y=v,b=x,w=f[3],M=f[4],_=f[5];s=c?l(f[6],f[7],m):{x:f[6],y:f[7]},this.command="A";var S=function(t,e,n,i,o,a,s){var c=r.mod(r.toRadian(s),2*Math.PI),u=t.x,h=t.y,l=e.x,d=e.y,f=Math.cos(c)*(u-l)/2+Math.sin(c)*(h-d)/2,m=-1*Math.sin(c)*(u-l)/2+Math.cos(c)*(h-d)/2,v=f*f/(o*o)+m*m/(a*a);v>1&&(o*=Math.sqrt(v),a*=Math.sqrt(v));var x=o*o*(m*m)+a*a*(f*f),y=Math.sqrt((o*o*(a*a)-x)/x);n===i&&(y*=-1),isNaN(y)&&(y=0);var b=y*o*m/a,w=y*-a*f/o,M=(u+l)/2+Math.cos(c)*b-Math.sin(c)*w,_=(h+d)/2+Math.sin(c)*b+Math.cos(c)*w,S=p([1,0],[(f-b)/o,(m-w)/a]),A=[(f-b)/o,(m-w)/a],E=[(-1*f-b)/o,(-1*m-w)/a],P=p(A,E);return g(A,E)<=-1&&(P=Math.PI),g(A,E)>=1&&(P=0),0===i&&P>0&&(P-=2*Math.PI),1===i&&P<0&&(P+=2*Math.PI),[t,M,_,o,a,S,P,c,i]}(m,s,M,_,y,b,w);this.params=S;var A=e.subStart;this.subStart=A,this.endPoint=s;var E=S[5]%(2*Math.PI);r.isNumberEqual(E,2*Math.PI)&&(E=0);var P=S[6]%(2*Math.PI);r.isNumberEqual(P,2*Math.PI)&&(P=0);var C=.001;this.startTangent=function(){0===_&&(C*=-1);var t=S[3]*Math.cos(E-C)+S[1],e=S[4]*Math.sin(E-C)+S[2];return[t-A.x,e-A.y]},this.endTangent=function(){var t=S[6];t-2*Math.PI<1e-4&&(t=0);var e=S[3]*Math.cos(E+t+C)+S[1],n=S[4]*Math.sin(E+t-C)+S[2];return[m.x-e,m.y-n]};break;case"Z":this.command="Z",this.params=[m,e.subStart],this.subStart=e.subStart,this.endPoint=e.subStart}},isInside:function(t,e,n){var r=this.command,o=this.params,a=this.box;if(a&&!i.box(a.minX,a.maxX,a.minY,a.maxY,t,e))return!1;switch(r){default:break;case"M":return!1;case"TL":case"L":case"Z":return i.line(o[0].x,o[0].y,o[1].x,o[1].y,n,t,e);case"SQ":case"Q":return i.quadraticline(o[0].x,o[0].y,o[1].x,o[1].y,o[2].x,o[2].y,n,t,e);case"C":return i.cubicline(o[0].x,o[0].y,o[1].x,o[1].y,o[2].x,o[2].y,o[3].x,o[3].y,n,t,e);case"A":var s=o,h=s[1],l=s[2],d=s[3],f=s[4],g=s[5],p=s[6],m=s[7],v=s[8],x=d>f?d:f,y=d>f?1:d/f,b=d>f?f/d:1;s=[t,e,1];var w=[1,0,0,0,1,0,0,0,1];return u.translate(w,w,[-h,-l]),u.rotate(w,w,-m),u.scale(w,w,[1/y,1/b]),c.transformMat3(s,s,w),i.arcline(0,0,x,g,g+p,1-v,n,s[0],s[1])}return!1},draw:function(t){var e,n,r,i=this.command,o=this.params;switch(i){default:break;case"M":t.moveTo(o[1].x,o[1].y);break;case"TL":case"L":t.lineTo(o[1].x,o[1].y);break;case"SQ":case"Q":e=o[1],n=o[2],t.quadraticCurveTo(e.x,e.y,n.x,n.y);break;case"C":e=o[1],n=o[2],r=o[3],t.bezierCurveTo(e.x,e.y,n.x,n.y,r.x,r.y);break;case"A":var a=o,s=a[1],c=a[2],u=a[3],h=a[4],l=a[5],d=a[6],f=a[7],g=a[8],p=u>h?u:h,m=u>h?1:u/h,v=u>h?h/u:1;t.translate(s,c),t.rotate(f),t.scale(m,v),t.arc(0,0,p,l,l+d,1-g),t.scale(1/m,1/v),t.rotate(-f),t.translate(-s,-c);break;case"Z":t.closePath()}},getBBox:function(t){var e,n,r,i,c=t/2,u=this.params;switch(this.command){default:case"M":case"Z":break;case"TL":case"L":this.box={minX:Math.min(u[0].x,u[1].x)-c,maxX:Math.max(u[0].x,u[1].x)+c,minY:Math.min(u[0].y,u[1].y)-c,maxY:Math.max(u[0].y,u[1].y)+c};break;case"SQ":case"Q":for(r=0,i=(n=a.extrema(u[0].x,u[1].x,u[2].x)).length;r_&&(_=E)}var P=s.yExtrema(v,f,g),C=1/0,O=-1/0,I=[y,b];for(r=2*-Math.PI;r<=2*Math.PI;r+=Math.PI){var k=P+r;1===x?yO&&(O=B)}this.box={minX:M-c,maxX:_+c,minY:C-c,maxY:O+c}}}}),t.exports=m},function(t,e,n){var r=n(593),i=n(600),o=Math.PI,a=Math.sin,s=Math.cos,c=Math.atan2,u=o/3;function h(t,e,n,r,i,h,l){var d,f,g,p,m,v,x;if(!e.fill){var y=e.arrowLength||10,b=e.arrowAngle?e.arrowAngle*o/180:u;x=c(r-h,n-i),m=Math.abs(e.lineWidth*s(x))/2,v=Math.abs(e.lineWidth*a(x))/2,l&&(m=-m,v=-v),d=i+y*s(x+b/2),f=h+y*a(x+b/2),g=i+y*s(x-b/2),p=h+y*a(x-b/2),t.beginPath(),t.moveTo(d-m,f-v),t.lineTo(i-m,h-v),t.lineTo(g-m,p-v),t.moveTo(i-m,h-v),t.lineTo(i+m,h+v),t.moveTo(i,h),t.stroke()}}function l(t,e,n,o,a,s,c){var u=c?e.startArrow:e.endArrow,h=u.d,l=0,d=a-n,f=s-o,g=Math.atan(d/f);0===f&&d<0?l=Math.PI:d>0&&f>0?l=Math.PI/2-g:d<0&&f<0?l=-Math.PI/2-g:d>=0&&f<0?l=-g-Math.PI/2:d<=0&&f>0&&(l=Math.PI/2-g);var p=function(t){var e,n=[],o=r.parsePath(t.path);if(!Array.isArray(o)||0===o.length||"M"!==o[0][0]&&"m"!==o[0][0])return!1;for(var a=o.length,s=0;sn&&(i=2*Math.PI-t+e,o=t-n):(i=t-e,o=n-t),i>o?n:e}function a(t,e,n,i){var a=0;return n-e>=2*Math.PI&&(a=2*Math.PI),e=r.mod(e,2*Math.PI),n=r.mod(n,2*Math.PI)+a,t=r.mod(t,2*Math.PI),i?e>=n?t>n&&tn?t:o(t,e,n):e<=n?ee||tt.x&&(g=t.x),pt.y&&(m=t.y),v-1}},function(t,e){function n(t,e){for(var n in e)e.hasOwnProperty(n)&&"constructor"!==n&&void 0!==e[n]&&(t[n]=e[n])}t.exports=function(t,e,r,i){return e&&n(t,e),r&&n(t,r),i&&n(t,i),t}},,function(t,e,n){var r=n(26),i=function(){function t(t){r.mix(this,{id:"",type:null,model:{},group:null,animate:!1,modelCache:{},isItem:!0,visible:!0},t),this._init()}var e=t.prototype;return e._init=function(){this._initGroup(),this.draw()},e._mapping=function(){var t=this.mapper,e=this.model;t.mapping(e)},e._initGroup=function(){var t=this.group,e=this.model,n=this.type;t.isItemContainer=!0,t.id=e.id,t.itemType=n,t.model=e,t.item=this},e._calculateBBox=function(){var t=this.keyShape,e=this.group,n=r.getBBox(t,e);return n.width=n.maxX-n.minX,n.height=n.maxY-n.minY,n.centerX=(n.minX+n.maxX)/2,n.centerY=(n.minY+n.maxY)/2,n},e.getLabel=function(){return this.group.findByClass("label")[0]},e.getGraph=function(){return this.graph},e._setShapeObj=function(){var t=this.graph,e=this.type,n=this.getModel();this.shapeObj=t.getShapeObj(e,n)},e._afterDraw=function(){var t=this.graph;this._setGId(),this._cacheModel(),t.emit("afteritemdraw",{item:this})},e._cacheModel=function(){this.modelCache=r.mix({},this.model)},e._setGId=function(){var t=this.group,e=this.id,n=this.type;t.gid=e,t.deepEach((function(t,r,i){var o=r.gid;if(t.id=e,t.eventPreFix=n,t.gid=o+"-"+i,t.isShape){var a=t.get("type");t.gid+="-"+a}}))},e._beforeDraw=function(){var t=this.graph,e=this.group;t.emit("beforeitemdraw",{item:this}),e.resetMatrix(),this.updateCollapsedParent()},e._shouldDraw=function(){return!0},e._getDiff=function(){var t=[],e=this.model,n=this.modelCache;return r.each(e,(function(e,i){r.isEqual(e,n[i])||t.push(i)})),0!==t.length&&t},e._drawInner=function(){var t=this.animate;this.group.clear(!t),this._mapping(),this._setShapeObj();var e=this.shapeObj,n=e.draw(this);n&&(n.isKeyShape=!0,this.keyShape=n),e.afterDraw&&e.afterDraw(this)},e.deepEach=function(t,e){r.traverseTree(this,t,e||function(t){return t.getChildren()})},e.getShapeObj=function(){return this.shapeObj},e.updateCollapsedParent=function(){var t=this.dataMap;this.collapsedParent=function t(e,n){var r=n[e.parent];if(!r)return!1;if(r){var i=t(r,n);if(i)return i}return r.collapsed?r:void 0}(this.model,t)},e.isVisible=function(){return this.visible},e.hide=function(){var t=this.group,e=this.graph;e.emit("beforeitemhide",{item:this}),t.hide(),this.visible=!1,e.emit("afteritemhide",{item:this})},e.show=function(){var t=this.group,e=this.graph;e.emit("beforeitemshow",{item:this}),t.show(),this.visible=!0,e.emit("afteritemshow",{item:this})},e.draw=function(){this._beforeDraw(),this._shouldDraw()&&this._drawInner(),this._afterDraw()},e.forceUpdate=function(){this._beforeDraw(),this._drawInner(),this._afterDraw()},e.getCenter=function(){var t=this.getBBox();return{x:t.centerX,y:t.centerY}},e.getBBox=function(){return this.bbox||this._calculateBBox()},e.layoutUpdate=function(){this.isVisible()&&this.draw()},e.update=function(){this.draw()},e.getModel=function(){return this.model},e.getKeyShape=function(){return this.keyShape},e.getGraphicGroup=function(){return this.group},e.getHierarchy=function(){return this.graph.getHierarchy(this)},e.getParent=function(){var t=this.model;return this.itemMap[t.parent]},e.getAllParents=function(){for(var t=this.model,e=this.itemMap,n=[],r=t.parent;r&&e[r];){var i=e[r],o=i.getModel();n.push(i),r=o.parent}return n},e.getAllChildren=function(){var t=[];return this.deepEach((function(e){t.push(e)})),t},e.getChildren=function(){var t=this.id;return this.graph.getItems().filter((function(e){return e.model.parent===t}))},e.toFront=function(){this.group.toFront()},e.toBack=function(){this.group.toBack()},e.destroy=function(){if(!this.destroyed){var t=this.animate,e=this.graph;e.emit("beforeitemdestroy",{item:this}),this.group.remove(!t),this.destroyed=!0,e.emit("afteritemdestroy",{item:this})}},t}();t.exports=i},function(t,e){t.exports="2.2.6"},function(t,e,n){var r=n(644),i="\t\n\v\f\r   ᠎              \u2028\u2029",o=new RegExp("([a-z])["+i+",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?["+i+"]*,?["+i+"]*)+)","ig"),a=new RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)["+i+"]*,?["+i+"]*","ig"),s=function(t){if(!t)return null;if(typeof t==typeof[])return t;var e={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},n=[];return String(t).replace(o,(function(t,r,i){var o=[],s=r.toLowerCase();if(i.replace(a,(function(t,e){e&&o.push(+e)})),"m"===s&&o.length>2&&(n.push([r].concat(o.splice(0,2))),s="l",r="m"===r?"l":"L"),"o"===s&&1===o.length&&n.push([r,o[0]]),"r"===s)n.push([r].concat(o));else for(;o.length>=e[s]&&(n.push([r].concat(o.splice(0,e[s]))),e[s]););})),n},c=function(t,e){for(var n=[],r=0,i=t.length;i-2*!e>r;r+=2){var o=[{x:+t[r-2],y:+t[r-1]},{x:+t[r],y:+t[r+1]},{x:+t[r+2],y:+t[r+3]},{x:+t[r+4],y:+t[r+5]}];e?r?i-4===r?o[3]={x:+t[0],y:+t[1]}:i-2===r&&(o[2]={x:+t[0],y:+t[1]},o[3]={x:+t[2],y:+t[3]}):o[0]={x:+t[i-2],y:+t[i-1]}:i-4===r?o[3]=o[2]:r||(o[0]={x:+t[r],y:+t[r+1]}),n.push(["C",(-o[0].x+6*o[1].x+o[2].x)/6,(-o[0].y+6*o[1].y+o[2].y)/6,(o[1].x+6*o[2].x-o[3].x)/6,(o[1].y+6*o[2].y-o[3].y)/6,o[2].x,o[2].y])}return n},u=function(t,e,n,r,i){var o=[];if(null===i&&null===r&&(r=n),t=+t,e=+e,n=+n,r=+r,null!==i){var a=Math.PI/180,s=t+n*Math.cos(-r*a),c=t+n*Math.cos(-i*a);o=[["M",s,e+n*Math.sin(-r*a)],["A",n,n,0,+(i-r>180),0,c,e+n*Math.sin(-i*a)]]}else o=[["M",t,e],["m",0,-r],["a",n,r,0,1,1,0,2*r],["a",n,r,0,1,1,0,-2*r],["z"]];return o},h=function(t){if(!(t=s(t))||!t.length)return[["M",0,0]];var e,n,r=[],i=0,o=0,a=0,h=0,l=0;"M"===t[0][0]&&(a=i=+t[0][1],h=o=+t[0][2],l++,r[0]=["M",i,o]);for(var d,f,g=3===t.length&&"M"===t[0][0]&&"R"===t[1][0].toUpperCase()&&"Z"===t[2][0].toUpperCase(),p=l,m=t.length;p1&&(r*=M=Math.sqrt(M),i*=M);var _=r*r,S=i*i,A=(a===s?-1:1)*Math.sqrt(Math.abs((_*S-_*w*w-S*b*b)/(_*w*w+S*b*b)));g=A*r*w/i+(e+c)/2,p=A*-i*b/r+(n+u)/2,d=Math.asin(((n-p)/i).toFixed(9)),f=Math.asin(((u-p)/i).toFixed(9)),d=ef&&(d-=2*Math.PI),!s&&f>d&&(f-=2*Math.PI)}var E=f-d;if(Math.abs(E)>m){var P=f,C=c,O=u;f=d+m*(s&&f>d?1:-1),x=t(c=g+r*Math.cos(f),u=p+i*Math.sin(f),r,i,o,0,s,C,O,[f,P,g,p])}E=f-d;var I=Math.cos(d),k=Math.sin(d),B=Math.cos(f),T=Math.sin(f),N=Math.tan(E/4),L=4/3*r*N,Y=4/3*i*N,X=[e,n],G=[e+L*k,n-Y*I],D=[c+L*T,u-Y*B],F=[c,u];if(G[0]=2*X[0]-G[0],G[1]=2*X[1]-G[1],h)return[G,D,F].concat(x);for(var j=[],R=0,H=(x=[G,D,F].concat(x).join().split(",")).length;R7){t[e].shift();for(var o=t[e];o.length;)s[e]="A",i&&(c[e]="A"),t.splice(e++,0,["C"].concat(o.splice(0,6)));t.splice(e,1),n=Math.max(r.length,i&&i.length||0)}},m=function(t,e,o,a,s){t&&e&&"M"===t[s][0]&&"M"!==e[s][0]&&(e.splice(s,0,["M",a.x,a.y]),o.bx=0,o.by=0,o.x=t[s][1],o.y=t[s][2],n=Math.max(r.length,i&&i.length||0))};n=Math.max(r.length,i&&i.length||0);for(var v=0;v1?1:c<0?0:c)/2,h=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],l=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],d=0,f=0;f<12;f++){var g=u*h[f]+u,p=m(g,t,n,i,a),v=m(g,e,r,o,s),x=p*p+v*v;d+=l[f]*Math.sqrt(x)}return u*d},x=function(t,e,n,r,i,o,a,s){if(!(Math.max(t,n)Math.max(i,a)||Math.max(e,r)Math.max(o,s))){var c=(t-n)*(o-s)-(e-r)*(i-a);if(c){var u=((t*r-e*n)*(i-a)-(t-n)*(i*s-o*a))/c,h=((t*r-e*n)*(o-s)-(e-r)*(i*s-o*a))/c,l=+u.toFixed(2),d=+h.toFixed(2);if(!(l<+Math.min(t,n).toFixed(2)||l>+Math.max(t,n).toFixed(2)||l<+Math.min(i,a).toFixed(2)||l>+Math.max(i,a).toFixed(2)||d<+Math.min(e,r).toFixed(2)||d>+Math.max(e,r).toFixed(2)||d<+Math.min(o,s).toFixed(2)||d>+Math.max(o,s).toFixed(2)))return{x:u,y:h}}}},y=function(t,e,n){return e>=t.x&&e<=t.x+t.width&&n>=t.y&&n<=t.y+t.height},b=function(t,e,n,r,i){if(i)return[["M",+t+ +i,e],["l",n-2*i,0],["a",i,i,0,0,1,i,i],["l",0,r-2*i],["a",i,i,0,0,1,-i,i],["l",2*i-n,0],["a",i,i,0,0,1,-i,-i],["l",0,2*i-r],["a",i,i,0,0,1,i,-i],["z"]];var o=[["M",t,e],["l",n,0],["l",0,r],["l",-n,0],["z"]];return o.parsePathArray=p,o},w=function(t,e,n,r){return null===t&&(t=e=n=r=0),null===e&&(e=t.y,n=t.width,r=t.height,t=t.x),{x:t,y:e,width:n,w:n,height:r,h:r,x2:t+n,y2:e+r,cx:t+n/2,cy:e+r/2,r1:Math.min(n,r)/2,r2:Math.max(n,r)/2,r0:Math.sqrt(n*n+r*r)/2,path:b(t,e,n,r),vb:[t,e,n,r].join(" ")}},M=function(t,e,n,i,o,a,s,c){r.isArray(t)||(t=[t,e,n,i,o,a,s,c]);var u=function(t,e,n,r,i,o,a,s){for(var c,u,h,l,d=[],f=[[],[]],g=0;g<2;++g)if(0===g?(u=6*t-12*n+6*i,c=-3*t+9*n-9*i+3*a,h=3*n-3*t):(u=6*e-12*r+6*o,c=-3*e+9*r-9*o+3*s,h=3*r-3*e),Math.abs(c)<1e-12){if(Math.abs(u)<1e-12)continue;(l=-h/u)>0&&l<1&&d.push(l)}else{var p=u*u-4*h*c,m=Math.sqrt(p);if(!(p<0)){var v=(-u+m)/(2*c);v>0&&v<1&&d.push(v);var x=(-u-m)/(2*c);x>0&&x<1&&d.push(x)}}for(var y,b=d.length,w=b;b--;)y=1-(l=d[b]),f[0][b]=y*y*y*t+3*y*y*l*n+3*y*l*l*i+l*l*l*a,f[1][b]=y*y*y*e+3*y*y*l*r+3*y*l*l*o+l*l*l*s;return f[0][w]=t,f[1][w]=e,f[0][w+1]=a,f[1][w+1]=s,f[0].length=f[1].length=w+2,{min:{x:Math.min.apply(0,f[0]),y:Math.min.apply(0,f[1])},max:{x:Math.max.apply(0,f[0]),y:Math.max.apply(0,f[1])}}}.apply(null,t);return w(u.min.x,u.min.y,u.max.x-u.min.x,u.max.y-u.min.y)},_=function(t,e,n,r,i,o,a,s,c){var u=1-c,h=Math.pow(u,3),l=Math.pow(u,2),d=c*c,f=d*c,g=t+2*c*(n-t)+d*(i-2*n+t),p=e+2*c*(r-e)+d*(o-2*r+e),m=n+2*c*(i-n)+d*(a-2*i+n),v=r+2*c*(o-r)+d*(s-2*o+r);return{x:h*t+3*l*c*n+3*u*c*c*i+f*a,y:h*e+3*l*c*r+3*u*c*c*o+f*s,m:{x:g,y:p},n:{x:m,y:v},start:{x:u*t+c*n,y:u*e+c*r},end:{x:u*i+c*a,y:u*o+c*s},alpha:90-180*Math.atan2(g-m,p-v)/Math.PI}},S=function(t,e,n){if(!function(t,e){return t=w(t),e=w(e),y(e,t.x,t.y)||y(e,t.x2,t.y)||y(e,t.x,t.y2)||y(e,t.x2,t.y2)||y(t,e.x,e.y)||y(t,e.x2,e.y)||y(t,e.x,e.y2)||y(t,e.x2,e.y2)||(t.xe.x||e.xt.x)&&(t.ye.y||e.yt.y)}(M(t),M(e)))return n?0:[];for(var r=~~(v.apply(0,t)/8),i=~~(v.apply(0,e)/8),o=[],a=[],s={},c=n?0:[],u=0;u=0&&C<=1&&O>=0&&O<=1&&(n?c++:c.push({x:P.x,y:P.y,t1:C,t2:O}))}}return c};function A(t,e){var n=[],r=[];return t.length&&function t(e,i){if(1===e.length)n.push(e[0]),r.push(e[0]);else{for(var o=[],a=0;a=3&&(3===t.length&&e.push("Q"),e=e.concat(t[1])),2===t.length&&e.push("L"),e.concat(t[t.length-1])}))}(t,e,n));else{var i=[].concat(t);"M"===i[0]&&(i[0]="L");for(var o=0;o<=n-1;o++)r.push(i)}return r}(t[i],t[i+1],r))}),[]);return u.unshift(t[0]),"Z"!==e[r]&&"z"!==e[r]||u.push("Z"),u},fillPathByDiff:function(t,e){var n=function(t,e){var n,r,i=t.length,o=e.length,a=0;if(0===i||0===o)return null;for(var s=[],c=0;c<=i;c++)s[c]=[],s[c][0]={min:c};for(var u=0;u<=o;u++)s[0][u]={min:u};for(var h=1;h<=i;h++){n=t[h-1];for(var l=1;l<=o;l++){r=e[l-1],a=E(n,r)?0:1;var d=s[h-1][l].min+1,f=s[h][l-1].min+1,g=s[h-1][l-1].min+a;s[h][l]=P(d,f,g)}}return s}(t,e),r=t.length,i=e.length,o=[],a=1,s=1;if(n[r][i]!==r){for(var c=1;c<=r;c++){var u=n[c][c].min;s=c;for(var h=a;h<=i;h++)n[c][h].min=0;l--)a=o[l].index,"add"===o[l].type?t.splice(a,0,[].concat(t[a])):t.splice(a,1)}var d=i-(r=t.length);if(r0)){t[r]=e[r];break}n=C(n,t[r-1],1)}t[r]=["Q"].concat(n.reduce((function(t,e){return t.concat(e)}),[]));break;case"T":t[r]=["T"].concat(n[0]);break;case"C":if(n.length<3){if(!(r>0)){t[r]=e[r];break}n=C(n,t[r-1],2)}t[r]=["C"].concat(n.reduce((function(t,e){return t.concat(e)}),[]));break;case"S":if(n.length<2){if(!(r>0)){t[r]=e[r];break}n=C(n,t[r-1],1)}t[r]=["S"].concat(n.reduce((function(t,e){return t.concat(e)}),[]));break;default:t[r]=e[r]}return t},intersection:function(t,e){return function(t,e,n){var r,i,o,a,s,c,u,h,l,d;t=f(t),e=f(e);for(var g=[],p=0,m=t.length;p=0&&m=0&&o<=1&&l.push(o);else{var d=u*u-4*c*h;r.isNumberEqual(d,0)?l.push(-u/(2*c)):d>0&&(a=(-u-(s=Math.sqrt(d)))/(2*c),(o=(-u+s)/(2*c))>=0&&o<=1&&l.push(o),a>=0&&a<=1&&l.push(a))}return l},len:function(t,e,n,i,o,a,c,u,h){r.isNil(h)&&(h=1);for(var l=(h=h>1?1:h<0?0:h)/2,d=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],f=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],g=0,p=0;p<12;p++){var m=l*d[p]+l,v=s(m,t,n,o,c),x=s(m,e,i,a,u),y=v*v+x*x;g+=f[p]*Math.sqrt(y)}return l*g}}},function(t,e,n){var r=n(603),i=n(640),o=n(612),a=n(602);t.exports={line:function(t,e,n,i,o,a,s){var c=r.box(t,e,n,i,o);if(!this.box(c.minX,c.maxX,c.minY,c.maxY,a,s))return!1;var u=r.pointDistance(t,e,n,i,a,s);return!isNaN(u)&&u<=o/2},polyline:function(t,e,n,r){var i=t.length-1;if(i<1)return!1;for(var o=0;on?n:t}},function(t,e){var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};t.exports=function(t){return"object"===(void 0===t?"undefined":n(t))&&null!==t}},function(t,e,n){var r=n(46),i=n(118);t.exports=function(t,e){if(!i(t))return t;var n=[];return r(t,(function(t,r){e(t,r)&&n.push(t)})),n}},function(t,e){var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};t.exports=function(t){var e=void 0===t?"undefined":n(t);return null!==t&&"object"===e||"function"===e}},function(t,e){var n=function(){function t(){}return t.prototype.execute=function(){throw new Error("please override this method")},t}();t.exports=n},function(t,e,n){t.exports={CompactBoxTree:n(688),Dendrogram:n(687),IndentedTree:n(686),Mindmap:n(685),Base:n(622)}},function(t,e,n){var r=n(26),i=function(t){function e(e){var n={type:"node",isNode:!0,zIndex:3,edges:[],linkable:!0};return r.mix(n,e),t.call(this,n)||this}!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t);var n=e.prototype;return n.updatePosition=function(){var t=this.group,e=this.model;t.setMatrix([1,0,0,0,1,0,e.x?e.x:0,e.y?e.y:0,1]),this.bbox=this._calculateBBox()},n._shouldDraw=function(){var e=this._getDiff(),n=t.prototype._shouldDraw.call(this);return e&&!(2===e.length&&-1!==e.indexOf("x")&&-1!==e.indexOf("y"))&&!(1===e.length&&("x"===e[0]||"y"===e[0]))&&n},n._afterDraw=function(){this.updatePosition(),t.prototype._afterDraw.call(this)},n.layoutUpdate=function(){this._beforeDraw(),this._afterDraw()},n.getEdges=function(){var t=this;return this.graph.getEdges().filter((function(e){var n=e.getModel();return n.source===t.id||n.target===t.id}))},n.getInEdges=function(){var t=this;return this.getEdges().filter((function(e){return e.target===t}))},n.getOutEdges=function(){var t=this;return this.getEdges().filter((function(e){return e.source===t}))},n.getLinkPoints=function(t){var e=this.getAnchorPoints();if(r.isNumber(t)&&e[t])return[e[t]];var n=t.x,i=t.y,o=this.getBBox(),a=o.centerX,s=o.centerY,c=n-a,u=i-s,h=this.shapeObj,l=h.anchor||{},d=this.defaultIntersectBox,f=[];if(r.isEmpty(e)){switch(h.intersectBox||l.intersectBox||l.type||d){case"rect":f=[r.getIntersectPointRect(o,t)];break;case"path":if(this.keyShape&&"path"===this.keyShape.get("type")){var g=r.parsePathArray(["M",n,i,"L",a,s]);f=[r.intersection(g,this.keyShape.get("path"))]}break;default:f=[r.getIntersectPointCircle(n,i,o.centerX,o.centerY,Math.max(o.width,o.height)/2)]}r.isEmpty(f[0])&&(f=[{x:a,y:s}])}else f=e.map((function(t){var e=t.x-a,n=t.y-s,i=r.getArcOfVectors({x:c,y:u},{x:e,y:n});return r.mix({},t,{arc:i})})).sort((function(t,e){return t.arc-e.arc}));return f},n.getAnchorPoints=function(t){var e,n=this.shapeObj,i=this.getBBox(),o=[],a=n.anchor||{};return e=r.isArray(a)?a:r.isFunction(a)?a(this):r.isFunction(a.points)?a.points(this):a.points,r.each(e,(function(t,e){var n=r.mix({x:i.minX+t[0]*i.width,y:i.minY+t[1]*i.height},t[2],{index:e});o.push(n)})),this._anchorPoints=o,r.isNumber(t)?this._anchorPoints[t]:this._anchorPoints},e}(n(608));t.exports=i},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}n(715),n(714),n(713);var i=n(712),o=n(710),a=n(683),s=n(26),c=n(706),u=n(248),h=n(704),l=n(702),d=n(700),f=n(699),g=n(697),p=n(696),m=n(695),v=n(692),x=n(691),y=[p,l,d,h,m,v,n(690),x,f,g],b=function(t){!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(n,t);var e=n.prototype;function n(e){var n,r={};return y.forEach((function(t){s.mix(r,s.clone(t.CFG),e)})),(n=t.call(this,r)||this)._pluginInit(),n.emit("beforeinit"),n._init(),n.emit("afterinit"),n}return e.getDefaultCfg=function(){return{container:void 0,width:void 0,height:void 0,plugins:[],fontFamily:'"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", SimSun, "sans-serif"',nodeDefaultShape:void 0,edgeDefaultShape:void 0,groupDefaultShape:void 0,defaultIntersectBox:"circle",renderer:"canvas",_type:"graph",_controllers:{},_timers:{},_dataMap:{},_itemMap:{},_freezMap:{},_data:{},_delayRunObj:{}}},e._init=function(){var t=this;this._initData(),this._initContainer(),this._initCanvas(),y.forEach((function(e){e.INIT&&t[e.INIT]()})),this.initEvent()},e.initEvent=function(){},e._executeLayout=function(t,e,n,r){s.isFunction(t)?t(e,n,this):s.isObject(t)&&(t.nodes=e,t.edges=n,t.groups=r,t.graph=this,t.execute())},e._pluginInit=function(){var t=this;this.get("plugins").forEach((function(e){t._initPlugin(e)}))},e._initPlugin=function(t){t.graph=this,t.init&&t.init()},e._getTimer=function(t){return this.get("_timers")[t]},e._setTimer=function(t,e){this.get("_timers")[t]=e},e._getController=function(t){return this.get("_controllers")[t]},e._initContainer=function(){var t=this.get("container");t||(t=this.get("id")),t=s.initDOMContainer(t,"graph");var e=s.createDOM('
    ',{position:"relative"});t.appendChild(e),this.set("_containerDOM",t),this.set("_graphContainer",e)},e._initCanvas=function(){var t=this.get("_graphContainer"),e=this.get("width"),n=this.get("height"),r=this.get("fontFamily"),i=this.get("renderer"),o={width:e,height:n,fontFamily:r,renderer:i,eventEnable:!1,containerDOM:t};"svg"===i&&(o.pixelRatio=1);var a=new(0,u.Canvas)(o),s=a.get("el");s.style.top=0,s.style.left=0,s.style.overflow="hidden",this.set("_canvas",a);var c=this.getMouseEventWrapper();c.style.outline="none",c.style["user-select"]="none",c.setAttribute("tabindex",20);var h=a.addGroup(),l=h.addGroup();this.set("_itemGroup",l),this.set("_rootGroup",h)},e._initData=function(){this.set("_dataMap",{}),this.set("_itemMap",{_nodes:[],_edges:[],_groups:[],_guides:[]}),this.set("_data",{})},e._refresh=function(){},e.getKeyboardEventWrapper=function(){return this.get("keyboardEventWrapper")||this.getMouseEventWrapper()},e.getMouseEventWrapper=function(){return this.get("_canvas").get("el")},e.addPlugin=function(t){var e=this.get("plugins");this._initPlugin(t),e.push(t)},e.getGraphContainer=function(){return this.get("_graphContainer")},e._sortGroup=function(t){var e=this.get("_dataMap"),n={};t.forEach((function(t){var r=t.id,i=t.parent;for(n[r]=1;i&&e[i];)n[r]++,i=e[i].parent})),t.sort((function(t,e){return n[e.id]-n[t.id]}))},e._addItems=function(t,e){var n=this;this._addDatas(t,e),"group"===t&&this._sortGroup(e);var r=s.upperFirst(t),i=o[r],a=this.get("_itemMap"),c=this.get("_itemGroup"),u=this.get("_dataMap"),h=this.get("animate"),l=this.get("defaultIntersectBox");if(!i)throw new Error("please set valid item type!");e.forEach((function(e){var r=new i({id:e.id,type:t,model:e,group:c.addGroup(),graph:n,mapper:n._getController(t+"Mapper"),itemMap:a,animate:h,dataMap:u,defaultIntersectBox:l});a[e.id]=r,a["_"+t+"s"].push(r)}))},e._removeItems=function(t){var e=this.get("_dataMap"),n=this.get("_itemMap");t.forEach((function(t){delete e[t.id],delete n[t.id],s.Array.remove(n["_"+t.type+"s"],t),t.destroy()}))},e._updateItems=function(t,e){t.forEach((function(t,n){var r=e[n];r&&s.mix(t.getModel(),r),t.update()}))},e._getShowEdge=function(t){var e=t.getSource(),n=t.getTarget();return(e.linkable&&e.isVisible()||!e.linkable)&&(n.linkable&&n.isVisible()||!n.linkable)&&t},e._addDatas=function(t,e){var n=this.get("_dataMap");e.forEach((function(t){if(s.isNil(t.id)&&(t.id=s.guid()),n[t.id])throw new Error("id:"+t.id+" has already been set, please set new one");n[t.id]=t}))},e._drawInner=function(){var t=this.get("_data"),e=this.get("_itemGroup"),n=this.get("_dataMap"),r=this.get("_itemMap");t.nodes&&this._addItems("node",t.nodes),t.groups&&this._addItems("group",t.groups),t.edges&&this._addItems("edge",t.edges),t.guides&&this._addItems("guide",t.guides),e.sortBy((function(t){var e=t.id,i=r[e],o=n[e];return o&&!s.isNil(o.index)?o.index:!i||i.destroyed||s.isNil(i.zIndex)?void 0:i.zIndex}))},e._clearInner=function(){this.getItems().forEach((function(t){t&&t.destroy()}))},e.preventAnimate=function(t){return this.set("_forcePreventAnimate",!0),t(),this.set("_forcePreventAnimate",!1),this},e.getShapeObj=function(t,e){if(!s.isObject(t)){var n=s.upperFirst(t),r=a[n],i=this.get(t+"DefaultShape");return r.getShape(e.shape,i)}return t.getShapeObj()},e.getSource=function(){return this.get("_sourceData")},e.parseSource=function(t){return t},e.getCanvas=function(){return this.get("_canvas")},e.getRootGroup=function(){return this.get("_rootGroup")},e.getItemGroup=function(){return this.get("_itemGroup")},e.source=function(t){return this.emit("beforesource"),this.set("_data",t),this.set("_sourceData",t),this.emit("aftersource"),this},e.render=function(){return this.emit("beforerender"),this.emit("beforedrawinner"),this._drawInner(),this.emit("afterdrawinner"),this.emit("afterrender"),this},e.reRender=function(){var t=this.get("_sourceData");return this.read(t),this},e.setCapture=function(t){this.get("_rootGroup").set("capture",t)},e.destroy=function(){this.emit("beforedestroy");var e=this.get("_canvas"),n=this.get("_graphContainer"),r=this.get("_controllers"),i=this.get("_timers"),o=this.get("_windowForceResizeEvent"),a=this.get("plugins");return s.each(i,(function(t){clearTimeout(t)})),s.each(r,(function(t){t.destroy()})),a.forEach((function(t){t.destroy&&t.destroy()})),e&&e.destroy(),n.destroy(),window.removeEventListener("resize",o),this.emit("afterdestroy"),t.prototype.destroy.call(this),this},e.save=function(){var t={nodes:[],edges:[],groups:[],guides:[]};return this.get("_itemGroup").get("children").forEach((function(e,n){var r=e.model;if(r){var i=e.itemType,o=s.clone(r);o.index=n,t[i+"s"].push(o)}})),0===t.nodes.length&&delete t.nodes,0===t.edges.length&&delete t.edges,0===t.groups.length&&delete t.groups,0===t.guides.length&&delete t.guides,t},e.add=function(t,e){var n=[],r={action:"add",model:e,affectedItemIds:n};this.emit("beforechange",r);var i=this.get("_itemMap");this._addItems(t,[e]);var o=i[e.id];return o.getAllParents().forEach((function(t){t.update()})),r.item=o,n.push(e.id),this.emit("afterchange",r),o},e.remove=function(t){if((t=this.getItem(t))&&!t.destroyed){var e=[],n=[],r={action:"remove",item:t,affectedItemIds:n};if(t.isNode){var i=t.getEdges();e=e.concat(i)}if(t.isGroup){var o=t.getEdges(),a=t.getAllChildren(),c=t.getCrossEdges(),u=t.getInnerEdges();e=e.concat(o,a,c,u),e=s.uniq(e)}e.push(t);var h=t.getAllParents();return h.forEach((function(t){n.push(t.id)})),e.forEach((function(t){n.push(t.id)})),this.emit("beforechange",r),this._removeItems(e),h.forEach((function(t){t.update()})),this.emit("afterchange",r),this}},e.simpleUpdate=function(t,e){return this._updateItems([t],[e]),this.draw(),this},e.update=function(t,e){var n=this.get("_itemMap");if((t=this.getItem(t))&&!t.destroyed&&e){var r=this.get("animate"),i=[],o=[],a=[],c=t.getModel(),u=s.mix({},c),h={action:"update",item:t,originModel:u,updateModel:e,affectedItemIds:a},l=n[u.parent];if(i.push(t),o.push(e),a.push(t.id),l&&l!==parent&&s.isGroup(l)&&t.getAllParents().forEach((function(t){i.push(t),o.push(null),a.push(t.id)})),e.parent){var d=n[e.parent];if(!d)throw new Error("there is no "+e.parent+" exist, please add a new one!");i.push(d),o.push(null),a.push(d.id),d.getAllParents().forEach((function(t){i.push(t),o.push(null),a.push(t.id)}))}return(t.isNode||t.isGroup)&&t.getEdges().forEach((function(t){i.push(t),o.push(null),a.push(t.id)})),t.isGroup&&!s.isNil(e.collapsed)&&(r&&t.deepEach((function(t){a.push(t.id)})),t.getCrossEdges().forEach((function(t){i.push(t),o.push(null),a.push(t.id)}))),this.emit("beforechange",h),this._updateItems(i,o),this.emit("afterchange",h),this}},e.read=function(t){var e=this;if(!t)throw new Error("please read valid data!");var n={action:"changeData",data:t};return this.emit("beforechange",n),this.preventAnimate((function(){e.clear(),e.source(t),e.render()})),this.emit("afterchange",n),this},e.clear=function(){return this.emit("beforeclear"),this._clearInner(),this._initData(),this.emit("afterclear"),this},e.hide=function(t){var e=[],n=[],r={item:t=this.getItem(t),affectedItemIds:n};return e.push(t),t.isNode&&t.getEdges().forEach((function(t){e.push(t)})),t.isGroup&&(t.getEdges().forEach((function(t){e.push(t)})),t.deepEach((function(t){e.push(t)}))),(e=s.uniq(e)).forEach((function(t){n.push(t.id)})),this.emit("beforehide",r),e.forEach((function(t){t.hide()})),this.emit("afterhide",r),this},e.show=function(t){var e=this,n=[],r=[],i={item:t=this.getItem(t),affectedItemIds:r};if(t.visible=!0,t.isEdge){var o=this._getShowEdge(t);o&&n.push(o)}else n.push(t);return t.isNode&&t.getEdges().forEach((function(t){(t=e._getShowEdge(t))&&n.push(t)})),t.isGroup&&(t.getEdges().forEach((function(t){(t=e._getShowEdge(t))&&n.push(t)})),t.deepEach((function(t){n.push(t)}))),(n=s.uniq(n)).forEach((function(t){r.push(t.id)})),this.emit("beforeshow",i),n.forEach((function(t){t.show()})),this.emit("aftershow",i),this},e.getWidth=function(){return this.get("width")},e.getHeight=function(){return this.get("height")},e.changeSize=function(t,e){if(!(Math.abs(t)>=1/0||Math.abs(e)>=1/0)){var n=this.get("_canvas");return t===this.get("width")&&e===this.get("height")||(this.emit("beforechangesize"),n.changeSize(t,e),this.set("width",t),this.set("height",e),this.emit("afterchangesize"),this.draw()),this}console.warn("size parameter more than the maximum")},e.toFront=function(t){t=this.getItem(t);var e=this.get("_itemGroup"),n=t.getGraphicGroup();s.toFront(n,e),this.draw()},e.toBack=function(t){t=this.getItem(t);var e=this.get("_itemGroup"),n=t.getGraphicGroup();s.toBack(n,e),this.draw()},e.css=function(t){var e=this.getGraphContainer();s.modifyCSS(e,t)},e.saveImage=function(t){var e=this.getBBox(),n=this.getFitViewPadding();return new c(function(t){for(var e=1;e1?n*e+this._getSpaceingY()*(e-1):n},isHitBox:function(){return!1},calculateBox:function(){var t=this._attrs,e=this._cfg;e.attrs&&!e.hasUpdate||(this._assembleFont(),this._setAttrText()),t.textArr||this._setAttrText();var n=t.x,r=t.y,i=this.measureText();if(!i)return{minX:n,minY:r,maxX:n,maxY:r};var o=this._getTextHeight(),a=t.textAlign,s=t.textBaseline,c=this.getHitLineWidth(),u={x:n,y:r-o};a&&("end"===a||"right"===a?u.x-=i:"center"===a&&(u.x-=i/2)),s&&("top"===s?u.y+=o:"middle"===s&&(u.y+=o/2)),this.set("startPoint",u);var h=c/2;return{minX:u.x-h,minY:u.y-h,maxX:u.x+i+h,maxY:u.y+o+h}},_getSpaceingY:function(){var t=this._attrs,e=t.lineHeight,n=1*t.fontSize;return e?e-n:.14*n},drawInner:function(t){var e=this._attrs,n=this._cfg;n.attrs&&!n.hasUpdate||(this._assembleFont(),this._setAttrText()),t.font=e.font;var i=e.text;if(i){var o=e.textArr,a=e.x,s=e.y;if(t.beginPath(),this.hasStroke()){var c=e.strokeOpacity;r.isNil(c)||1===c||(t.globalAlpha=c),o?this._drawTextArr(t,!1):t.strokeText(i,a,s),t.globalAlpha=1}if(this.hasFill()){var u=e.fillOpacity;r.isNil(u)||1===u||(t.globalAlpha=u),o?this._drawTextArr(t,!0):t.fillText(i,a,s)}n.hasUpdate=!1}},_drawTextArr:function(t,e){var n,i=this._attrs.textArr,o=this._attrs.textBaseline,a=1*this._attrs.fontSize,s=this._getSpaceingY(),c=this._attrs.x,u=this._attrs.y,h=this.getBBox(),l=h.maxY-h.minY;r.each(i,(function(r,i){n=u+i*(s+a)-l+a,"middle"===o&&(n+=l-a-(l-a)/2),"top"===o&&(n+=l-a),e?t.fillText(r,c,n):t.strokeText(r,c,n)}))},measureText:function(){var t,e=this._attrs,n=e.text,i=e.font,o=e.textArr,a=0;if(!r.isNil(n)){var s=document.createElement("canvas").getContext("2d");return s.save(),s.font=i,o?r.each(o,(function(e){t=s.measureText(e).width,aa&&(a=e),ns&&(s=n)}));var c=e/2;return{minX:i-c,minY:o-c,maxX:a+c,maxY:s+c}},_setTcache:function(){var t,e,n=this._attrs.points,i=0,o=0,s=[];n&&0!==n.length&&(r.each(n,(function(t,e){n[e+1]&&(i+=a.len(t[0],t[1],n[e+1][0],n[e+1][1]))})),i<=0||(r.each(n,(function(r,c){n[c+1]&&((t=[])[0]=o/i,e=a.len(r[0],r[1],n[c+1][0],n[c+1][1]),o+=e,t[1]=o/i,s.push(t))})),this.tCache=s))},createPath:function(t){var e,n,r=this._attrs.points;if(!(r.length<2)){for((t=t||this.get("context")).beginPath(),t.moveTo(r[0][0],r[0][1]),n=1,e=r.length-1;n=r[0]&&t<=r[1]&&(e=(t-r[0])/(r[1]-r[0]),n=i)})),{x:a.at(i[n][0],i[n+1][0],e),y:a.at(i[n][1],i[n+1][1],e)}}}),t.exports=s},function(t,e,n){var r=n(19),i=n(58),o=function t(e){t.superclass.constructor.call(this,e)};o.ATTRS={points:null,lineWidth:1},r.extend(o,i),r.augment(o,{canFill:!0,canStroke:!0,type:"polygon",getDefaultAttrs:function(){return{lineWidth:1}},calculateBox:function(){var t=this._attrs.points,e=this.getHitLineWidth();if(!t||0===t.length)return null;var n=1/0,i=1/0,o=-1/0,a=-1/0;r.each(t,(function(t){var e=t[0],r=t[1];eo&&(o=e),ra&&(a=r)}));var s=e/2;return{minX:n-s,minY:i-s,maxX:o+s,maxY:a+s}},createPath:function(t){var e=this._attrs.points;e.length<2||((t=t||this.get("context")).beginPath(),r.each(e,(function(e,n){0===n?t.moveTo(e[0],e[1]):t.lineTo(e[0],e[1])})),t.closePath())}}),t.exports=o},function(t,e,n){var r=n(19),i=n(58),o=n(600),a=n(593),s=n(601),c=n(610),u=n(612),h=function t(e){t.superclass.constructor.call(this,e)};h.ATTRS={path:null,lineWidth:1,startArrow:!1,endArrow:!1},r.extend(h,i),r.augment(h,{canFill:!0,canStroke:!0,type:"path",getDefaultAttrs:function(){return{lineWidth:1,startArrow:!1,endArrow:!1}},_afterSetAttrPath:function(t){if(r.isNil(t))return this.setSilent("segments",null),void this.setSilent("box",void 0);var e,n=a.parsePath(t),i=[];if(r.isArray(n)&&0!==n.length&&("M"===n[0][0]||"m"===n[0][0])){for(var s=n.length,c=0;ci&&(i=r.maxX),r.minYa&&(a=r.maxY))})),n===1/0||o===1/0?{minX:0,minY:0,maxX:0,maxY:0}:{minX:n,minY:o,maxX:i,maxY:a}},_setTcache:function(){var t,e,n,i,o=0,a=0,s=[],c=this._cfg.curve;c&&(r.each(c,(function(t,e){n=c[e+1],i=t.length,n&&(o+=u.len(t[i-2],t[i-1],n[1],n[2],n[3],n[4],n[5],n[6]))})),r.each(c,(function(r,h){n=c[h+1],i=r.length,n&&((t=[])[0]=a/o,e=u.len(r[i-2],r[i-1],n[1],n[2],n[3],n[4],n[5],n[6]),a+=e,t[1]=a/o,s.push(t))})),this._cfg.tCache=s)},_calculateCurve:function(){var t=this._attrs.path;this._cfg.curve=c.pathTocurve(t)},getStartTangent:function(){var t,e,n,i,o=this.get("segments");if(o.length>1)if(t=o[0].endPoint,e=o[1].endPoint,n=o[1].startTangent,i=[],r.isFunction(n)){var a=n();i.push([t.x-a[0],t.y-a[1]]),i.push([t.x,t.y])}else i.push([e.x,e.y]),i.push([t.x,t.y]);return i},getEndTangent:function(){var t,e,n,i,o=this.get("segments"),a=o.length;if(a>1)if(t=o[a-2].endPoint,e=o[a-1].endPoint,n=o[a-1].endTangent,i=[],r.isFunction(n)){var s=n();i.push([e.x-s[0],e.y-s[1]]),i.push([e.x,e.y])}else i.push([t.x,t.y]),i.push([e.x,e.y]);return i},getPoint:function(t){var e,n,i=this._cfg.tCache;i||(this._calculateCurve(),this._setTcache(),i=this._cfg.tCache);var o=this._cfg.curve;if(!i)return o?{x:o[0][1],y:o[0][2]}:null;r.each(i,(function(r,i){t>=r[0]&&t<=r[1]&&(e=(t-r[0])/(r[1]-r[0]),n=i)}));var a=o[n];if(r.isNil(a)||r.isNil(n))return null;var s=a.length,c=o[n+1];return{x:u.at(a[s-2],c[1],c[3],c[5],1-e),y:u.at(a[s-1],c[2],c[4],c[6],1-e)}},createPath:function(t){var e=this.get("segments");if(r.isArray(e)){(t=t||this.get("context")).beginPath();for(var n=e.length,i=0;ia?o:a,c=o>a?1:o/a,u=o>a?a/o:1,h=[1,0,0,0,1,0,0,0,1];r.mat3.scale(h,h,[c,u]),r.mat3.translate(h,h,[n,i]),t.beginPath(),t.save(),t.transform(h[0],h[1],h[3],h[4],h[6],h[7]),t.arc(0,0,s,0,2*Math.PI),t.restore(),t.closePath()}}),t.exports=o},function(t,e,n){var r=n(19),i=n(58),o=function t(e){t.superclass.constructor.call(this,e)};r.extend(o,i),r.augment(o,{canFill:!0,canStroke:!0,type:"dom",calculateBox:function(){var t=this._attrs,e=t.x,n=t.y,r=t.width,i=t.height,o=this.getHitLineWidth()/2;return{minX:e-o,minY:n-o,maxX:e+r+o,maxY:n+i+o}}}),t.exports=o},function(t,e,n){var r=n(19),i=n(58),o=function t(e){t.superclass.constructor.call(this,e)};o.ATTRS={x:0,y:0,r:0,lineWidth:1},r.extend(o,i),r.augment(o,{canFill:!0,canStroke:!0,type:"circle",getDefaultAttrs:function(){return{lineWidth:1}},calculateBox:function(){var t=this._attrs,e=t.x,n=t.y,r=t.r,i=this.getHitLineWidth()/2+r;return{minX:e-i,minY:n-i,maxX:e+i,maxY:n+i}},createPath:function(t){var e=this._attrs,n=e.x,r=e.y,i=e.r;t.beginPath(),t.arc(n,r,i,0,2*Math.PI,!1),t.closePath()}}),t.exports=o},function(t,e,n){var r=n(19),i=n(58),o=n(602),a=n(601);function s(t,e,n){return t+e*Math.cos(n)}function c(t,e,n){return t+e*Math.sin(n)}var u=function t(e){t.superclass.constructor.call(this,e)};u.ATTRS={x:0,y:0,r:0,startAngle:0,endAngle:0,clockwise:!1,lineWidth:1,startArrow:!1,endArrow:!1},r.extend(u,i),r.augment(u,{canStroke:!0,type:"arc",getDefaultAttrs:function(){return{x:0,y:0,r:0,startAngle:0,endAngle:0,clockwise:!1,lineWidth:1,startArrow:!1,endArrow:!1}},calculateBox:function(){var t=this._attrs,e=t.x,n=t.y,r=t.r,i=t.startAngle,a=t.endAngle,s=t.clockwise,c=this.getHitLineWidth()/2,u=o.box(e,n,r,i,a,s);return u.minX-=c,u.minY-=c,u.maxX+=c,u.maxY+=c,u},getStartTangent:function(){var t=this._attrs,e=t.x,n=t.y,r=t.startAngle,i=t.r,o=t.clockwise,a=Math.PI/180;o&&(a*=-1);var u=[],h=s(e,i,r+a),l=c(n,i,r+a),d=s(e,i,r),f=c(n,i,r);return u.push([h,l]),u.push([d,f]),u},getEndTangent:function(){var t=this._attrs,e=t.x,n=t.y,r=t.endAngle,i=t.r,o=t.clockwise,a=Math.PI/180,u=[];o&&(a*=-1);var h=s(e,i,r+a),l=c(n,i,r+a),d=s(e,i,r),f=c(n,i,r);return u.push([d,f]),u.push([h,l]),u},createPath:function(t){var e=this._attrs,n=e.x,r=e.y,i=e.r,o=e.startAngle,a=e.endAngle,s=e.clockwise;(t=t||self.get("context")).beginPath(),t.arc(n,r,i,o,a,s)},afterPath:function(t){var e=this._attrs;if(t=t||this.get("context"),e.startArrow){var n=this.getStartTangent();a.addStartArrow(t,e,n[0][0],n[0][1],n[1][0],n[1][1])}if(e.endArrow){var r=this.getEndTangent();a.addEndArrow(t,e,r[0][0],r[0][1],r[1][0],r[1][1])}}}),t.exports=u},function(t,e){t.exports={xAt:function(t,e,n,r,i){return e*Math.cos(t)*Math.cos(i)-n*Math.sin(t)*Math.sin(i)+r},yAt:function(t,e,n,r,i){return e*Math.sin(t)*Math.cos(i)+n*Math.cos(t)*Math.sin(i)+r},xExtrema:function(t,e,n){return Math.atan(-n/e*Math.tan(t))},yExtrema:function(t,e,n){return Math.atan(n/(e*Math.tan(t)))}}},function(t,e,n){var r=n(19),i=r.vec2;function o(t,e,n,r){var i=1-r;return i*(i*t+2*r*e)+r*r*n}function a(t,e,n,r,a,s,c,u,h){var l,d,f,g,p,m,v,x=.005,y=1/0,b=[c,u];for(p=0;p<1;p+=.05)f=[o(t,n,a,p),o(e,r,s,p)],(d=i.squaredDistance(b,f))=0&&d=0?[o]:[]}}},function(t,e,n){var r=n(19),i=n(746),o=n(745),a=n(744),s=n(116),c=function(t){this._cfg={zIndex:0,capture:!0,visible:!0,destroyed:!1},r.assign(this._cfg,this.getDefaultCfg(),t),this.initAttrs(this._cfg.attrs),this._cfg.attrs={},this.initTransform(),this.init()};c.CFG={id:null,zIndex:0,canvas:null,parent:null,capture:!0,context:null,visible:!0,destroyed:!1},r.augment(c,i,o,s,a,{init:function(){this.setSilent("animable",!0),this.setSilent("animating",!1)},getParent:function(){return this._cfg.parent},getDefaultCfg:function(){return{}},set:function(t,e){return"zIndex"===t&&this._beforeSetZIndex&&this._beforeSetZIndex(e),"loading"===t&&this._beforeSetLoading&&this._beforeSetLoading(e),this._cfg[t]=e,this},setSilent:function(t,e){this._cfg[t]=e},get:function(t){return this._cfg[t]},show:function(){return this._cfg.visible=!0,this},hide:function(){return this._cfg.visible=!1,this},remove:function(t,e){var n=this._cfg,i=n.parent,o=n.el;return i&&r.remove(i.get("children"),this),o&&(e?i&&i._cfg.tobeRemoved.push(o):o.parentNode.removeChild(o)),(t||void 0===t)&&this.destroy(),this},destroy:function(){this.get("destroyed")||(this._attrs=null,this.removeEvent(),this._cfg={destroyed:!0})},toFront:function(){var t=this._cfg,e=t.parent;if(e){var n=e._cfg.children,r=t.el,i=n.indexOf(this);n.splice(i,1),n.push(this),r&&(r.parentNode.removeChild(r),t.el=null)}},toBack:function(){var t=this._cfg,e=t.parent;if(e){var n=e._cfg.children,r=t.el,i=n.indexOf(this);if(n.splice(i,1),n.unshift(this),r){var o=r.parentNode;o.removeChild(r),o.insertBefore(r,o.firstChild)}}},_beforeSetZIndex:function(t){var e=this._cfg.parent;this._cfg.zIndex=t,r.isNil(e)||e.sort();var n=this._cfg.el;if(n){var i=e._cfg.children,o=i.indexOf(this),a=n.parentNode;a.removeChild(n),o===i.length-1?a.appendChild(n):a.insertBefore(n,a.childNodes[o])}return t},_setAttrs:function(t){return this.attr(t),t},setZIndex:function(t){return this._cfg.zIndex=t,this._beforeSetZIndex(t)},clone:function(){return r.clone(this)},getBBox:function(){}}),t.exports=c},function(t,e,n){var r=n(19),i=n(641),o=n(743),a={},s="_INDEX";function c(t,e,n){for(var r,i=t.length-1;i>=0;i--){var o=t[i];if(o._cfg.visible&&o._cfg.capture&&(o.isGroup?r=o.getShape(e,n):o.isHit(e,n)&&(r=o)),r)break}return r}var u=function t(e){t.superclass.constructor.call(this,e),this.set("children",[]),this.set("tobeRemoved",[]),this._beforeRenderUI(),this._renderUI(),this._bindUI()};r.extend(u,i),r.augment(u,{isGroup:!0,type:"group",canFill:!0,canStroke:!0,getDefaultCfg:function(){return function t(e){if(!e._cfg&&e!==u){var n=e.superclass.constructor;n&&!n._cfg&&t(n),e._cfg={},r.merge(e._cfg,n._cfg),r.merge(e._cfg,e.CFG)}}(this.constructor),r.merge({},this.constructor._cfg)},_beforeRenderUI:function(){},_renderUI:function(){},_bindUI:function(){},addShape:function(t,e){var n=this.get("canvas");e=e||{};var i=a[t];if(i||(i=r.upperFirst(t),a[t]=i),e.attrs&&n){var s=e.attrs;if("text"===t){var c=n.get("fontFamily");c&&(s.fontFamily=s.fontFamily?s.fontFamily:c)}}e.canvas=n,e.type=t;var u=new o[i](e);return this.add(u),u},addGroup:function(t,e){var n,i=this.get("canvas");if(e=r.merge({},e),r.isFunction(t))e?(e.canvas=i,e.parent=this,n=new t(e)):n=new t({canvas:i,parent:this}),this.add(n);else if(r.isObject(t))t.canvas=i,n=new u(t),this.add(n);else{if(void 0!==t)return!1;n=new u,this.add(n)}return n},renderBack:function(t,e){var n=this.get("backShape"),i=this.getBBox();return r.merge(e,{x:i.minX-t[3],y:i.minY-t[0],width:i.width+t[1]+t[3],height:i.height+t[0]+t[2]}),n?n.attr(e):n=this.addShape("rect",{zIndex:-1,attrs:e}),this.set("backShape",n),this.sort(),n},removeChild:function(t,e){if(arguments.length>=2)this.contain(t)&&t.remove(e);else{if(1===arguments.length){if(!r.isBoolean(t))return this.contain(t)&&t.remove(!0),this;e=t}0===arguments.length&&(e=!0),u.superclass.remove.call(this,e)}return this},add:function(t){var e=this,n=e.get("children");if(r.isArray(t))r.each(t,(function(t){var n=t.get("parent");n&&n.removeChild(t,!1),e._setCfgProperty(t)})),e._cfg.children=n.concat(t);else{var i=t,o=i.get("parent");o&&o.removeChild(i,!1),e._setCfgProperty(i),n.push(i)}return e},_setCfgProperty:function(t){var e=this._cfg;t.set("parent",this),t.set("canvas",e.canvas),e.timeline&&t.set("timeline",e.timeline)},contain:function(t){return this.get("children").indexOf(t)>-1},getChildByIndex:function(t){return this.get("children")[t]},getFirst:function(){return this.getChildByIndex(0)},getLast:function(){var t=this.get("children").length-1;return this.getChildByIndex(t)},getBBox:function(){var t=1/0,e=-1/0,n=1/0,i=-1/0,o=this.get("children");o.length>0?r.each(o,(function(r){if(r.get("visible")){if(r.isGroup&&0===r.get("children").length)return;var o=r.getBBox();if(!o)return!0;var a=[o.minX,o.minY,1],s=[o.minX,o.maxY,1],c=[o.maxX,o.minY,1],u=[o.maxX,o.maxY,1];r.apply(a),r.apply(s),r.apply(c),r.apply(u);var h=Math.min(a[0],s[0],c[0],u[0]),l=Math.max(a[0],s[0],c[0],u[0]),d=Math.min(a[1],s[1],c[1],u[1]),f=Math.max(a[1],s[1],c[1],u[1]);he&&(e=l),di&&(i=f)}})):(t=0,e=0,n=0,i=0);var a={minX:t,minY:n,maxX:e,maxY:i};return a.x=a.minX,a.y=a.minY,a.width=a.maxX-a.minX,a.height=a.maxY-a.minY,a},getCount:function(){return this.get("children").length},sort:function(){var t=this.get("children");return r.each(t,(function(t,e){return t[s]=e,t})),t.sort((function(t,e){var n=function(t,e){return t.get("zIndex")-e.get("zIndex")}(t,e);return 0===n?t[s]-e[s]:n})),this},findById:function(t){return this.find((function(e){return e.get("id")===t}))},find:function(t){if(r.isString(t))return this.findById(t);var e=this.get("children"),n=null;return r.each(e,(function(e){if(t(e)?n=e:e.find&&(n=e.find(t)),n)return!1})),n},findAll:function(t){var e=this.get("children"),n=[],i=[];return r.each(e,(function(e){t(e)&&n.push(e),e.findAllBy&&(i=e.findAllBy(t),n=n.concat(i))})),n},findBy:function(t){var e=this.get("children"),n=null;return r.each(e,(function(e){if(t(e)?n=e:e.findBy&&(n=e.findBy(t)),n)return!1})),n},findAllBy:function(t){var e=this.get("children"),n=[],i=[];return r.each(e,(function(e){t(e)&&n.push(e),e.findAllBy&&(i=e.findAllBy(t),n=n.concat(i))})),n},getShape:function(t,e){var n,r=this._attrs.clip,i=this._cfg.children;if(r){var o=[t,e,1];r.invert(o,this.get("canvas")),r.isPointInPath(o[0],o[1])&&(n=c(i,t,e))}else n=c(i,t,e);return n},clearTotalMatrix:function(){if(this.get("totalMatrix")){this.setSilent("totalMatrix",null);for(var t=this._cfg.children,e=0;e=0;n--)e[n].remove(!0,t);return this._cfg.children=[],this},destroy:function(){this.get("destroyed")||(this.clear(),u.superclass.destroy.call(this))},clone:function(){var t=this._cfg.children,e=new u;return r.each(t,(function(t){e.add(t.clone())})),e}}),t.exports=u},function(t,e,n){var r=n(19),i=function(t,e,n,r){this.type=t,this.target=null,this.currentTarget=null,this.bubbles=n,this.cancelable=r,this.timeStamp=(new Date).getTime(),this.defaultPrevented=!1,this.propagationStopped=!1,this.removed=!1,this.event=e};r.augment(i,{preventDefault:function(){this.defaultPrevented=this.cancelable&&!0},stopPropagation:function(){this.propagationStopped=!0},remove:function(){this.remove=!0},clone:function(){return r.clone(this)},toString:function(){return"[Event (type="+this.type+")]"}}),t.exports=i},function(t,e,n){t.exports={isFunction:n(84),isObject:n(621),isBoolean:n(653),isNil:n(237),isString:n(595),isArray:n(45),isNumber:n(236),isEmpty:n(647),uniqueId:n(646),clone:n(616),deepMix:n(615),assign:n(606),merge:n(615),upperFirst:n(655),each:n(46),isEqual:n(614),toArray:n(604),extend:n(650),augment:n(651),remove:n(676),isNumberEqual:n(673),toRadian:n(669),toDegree:n(671),mod:n(672),clamp:n(618),createDom:n(681),modifyCSS:n(680),requestAnimationFrame:n(679),getRatio:function(){return window.devicePixelRatio?window.devicePixelRatio:2},mat3:n(617),vec2:n(668),vec3:n(667),transform:n(666)}},function(t,e,n){var r=n(16),i=n(253),o=n(251),a=Math.max,s=Math.min;t.exports=function(t,e,n){var c,u,h,l,d,f,g=0,p=!1,m=!1,v=!0;if("function"!=typeof t)throw new TypeError("Expected a function");function x(e){var n=c,r=u;return c=u=void 0,g=e,l=t.apply(r,n)}function y(t){var n=t-f;return void 0===f||n>=e||n<0||m&&t-g>=h}function b(){var t=i();if(y(t))return w(t);d=setTimeout(b,function(t){var n=e-(t-f);return m?s(n,h-(t-g)):n}(t))}function w(t){return d=void 0,v&&c?x(t):(c=u=void 0,l)}function M(){var t=i(),n=y(t);if(c=arguments,u=this,f=t,n){if(void 0===d)return function(t){return g=t,d=setTimeout(b,e),p?x(t):l}(f);if(m)return d=setTimeout(b,e),x(f)}return void 0===d&&(d=setTimeout(b,e)),l}return e=o(e)||0,r(n)&&(p=!!n.leading,h=(m="maxWait"in n)?a(o(n.maxWait)||0,e):h,v="trailing"in n?!!n.trailing:v),M.cancel=function(){void 0!==d&&clearTimeout(d),g=0,c=f=u=d=void 0},M.flush=function(){return void 0===d?l:w(i())},M}},function(t,e){var n=function(){var t={};return function(e){return t[e=e||"g"]?t[e]+=1:t[e]=1,e+t[e]}}();t.exports=n},function(t,e,n){var r=n(237),i=n(118),o=n(654),a=n(652),s=Object.prototype.hasOwnProperty;t.exports=function(t){if(r(t))return!0;if(i(t))return!t.length;var e=o(t);if("Map"===e||"Set"===e)return!t.size;if(a(t))return!Object.keys(t).length;for(var n in t)if(s.call(t,n))return!1;return!0}},function(t,e,n){var r=n(46),i=n(45),o=Object.prototype.hasOwnProperty;t.exports=function(t,e){if(!e||!i(t))return t;var n={},a=null;return r(t,(function(t){a=e(t),o.call(n,a)?n[a].push(t):n[a]=[t]})),n}},function(t,e,n){var r=n(84),i=n(45),o=n(648);t.exports=function(t,e){if(!e)return{0:t};if(!r(e)){var n=i(e)?e:e.replace(/\s+/g,"").split("*");e=function(t){for(var e="_",r=0,i=n.length;rr;r+=2){var o=[{x:+t[r-2],y:+t[r-1]},{x:+t[r],y:+t[r+1]},{x:+t[r+2],y:+t[r+3]},{x:+t[r+4],y:+t[r+5]}];e?r?i-4===r?o[3]={x:+t[0],y:+t[1]}:i-2===r&&(o[2]={x:+t[0],y:+t[1]},o[3]={x:+t[2],y:+t[3]}):o[0]={x:+t[i-2],y:+t[i-1]}:i-4===r?o[3]=o[2]:r||(o[0]={x:+t[r],y:+t[r+1]}),n.push(["C",(-o[0].x+6*o[1].x+o[2].x)/6,(-o[0].y+6*o[1].y+o[2].y)/6,(o[1].x+6*o[2].x-o[3].x)/6,(o[1].y+6*o[2].y-o[3].y)/6,o[2].x,o[2].y])}return n}},function(t,e){var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r="\t\n\v\f\r   ᠎              \u2028\u2029",i=new RegExp("([a-z])["+r+",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?["+r+"]*,?["+r+"]*)+)","ig"),o=new RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)["+r+"]*,?["+r+"]*","ig");t.exports=function(t){if(!t)return null;if((void 0===t?"undefined":n(t))===n([]))return t;var e={a:7,c:6,o:2,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,u:3,z:0},r=[];return String(t).replace(i,(function(t,n,i){var a=[],s=n.toLowerCase();if(i.replace(o,(function(t,e){e&&a.push(+e)})),"m"===s&&a.length>2&&(r.push([n].concat(a.splice(0,2))),s="l",n="m"===n?"l":"L"),"o"===s&&1===a.length&&r.push([n,a[0]]),"r"===s)r.push([n].concat(a));else for(;a.length>=e[s]&&(r.push([n].concat(a.splice(0,e[s]))),e[s]););})),r}},function(t,e,n){var r=n(659),i=n(658);function o(t,e,n,r,i){var o=[];if(null===i&&null===r&&(r=n),t=+t,e=+e,n=+n,r=+r,null!==i){var a=Math.PI/180,s=t+n*Math.cos(-r*a),c=t+n*Math.cos(-i*a);o=[["M",s,e+n*Math.sin(-r*a)],["A",n,n,0,+(i-r>180),0,c,e+n*Math.sin(-i*a)]]}else o=[["M",t,e],["m",0,-r],["a",n,r,0,1,1,0,2*r],["a",n,r,0,1,1,0,-2*r],["z"]];return o}t.exports=function(t){if(!(t=r(t))||!t.length)return[["M",0,0]];var e=[],n=0,a=0,s=0,c=0,u=0,h=void 0,l=void 0;"M"===t[0][0]&&(s=n=+t[0][1],c=a=+t[0][2],u++,e[0]=["M",n,a]);for(var d,f,g=3===t.length&&"M"===t[0][0]&&"R"===t[1][0].toUpperCase()&&"Z"===t[2][0].toUpperCase(),p=u,m=t.length;p1&&(r*=M=Math.sqrt(M),i*=M);var _=r*r,S=i*i,A=(a===s?-1:1)*Math.sqrt(Math.abs((_*S-_*w*w-S*b*b)/(_*w*w+S*b*b)));v=A*r*w/i+(e+c)/2,x=A*-i*b/r+(n+u)/2,p=Math.asin(((n-x)/i).toFixed(9)),m=Math.asin(((u-x)/i).toFixed(9)),p=em&&(p-=2*Math.PI),!s&&m>p&&(m-=2*Math.PI)}var E=m-p;if(Math.abs(E)>l){var P=m,C=c,O=u;m=p+l*(s&&m>p?1:-1),f=t(c=v+r*Math.cos(m),u=x+i*Math.sin(m),r,i,o,0,s,C,O,[m,P,v,x])}E=m-p;var I=Math.cos(p),k=Math.sin(p),B=Math.cos(m),T=Math.sin(m),N=Math.tan(E/4),L=4/3*r*N,Y=4/3*i*N,X=[e,n],G=[e+L*k,n-Y*I],D=[c+L*T,u-Y*B],F=[c,u];if(G[0]=2*X[0]-G[0],G[1]=2*X[1]-G[1],h)return[G,D,F].concat(f);for(var j=[],R=0,H=(f=[G,D,F].concat(f).join().split(",")).length;R7){t[e].shift();for(var r=t[e];r.length;)u[e]="A",a&&(h[e]="A"),t.splice(e++,0,["C"].concat(r.splice(0,6)));t.splice(e,1),f=Math.max(n.length,a&&a.length||0)}},m=function(t,e,r,i,o){t&&e&&"M"===t[o][0]&&"M"!==e[o][0]&&(e.splice(o,0,["M",i.x,i.y]),r.bx=0,r.by=0,r.x=t[o][1],r.y=t[o][2],f=Math.max(n.length,a&&a.length||0))};f=Math.max(n.length,a&&a.length||0);for(var v=0;v=0;return n?o?2*Math.PI-i:i:o?i:2*Math.PI-i},r.vertical=function(t,e,n){return n?(t[0]=e[1],t[1]=-1*e[0]):(t[0]=-1*e[1],t[1]=e[0]),t},t.exports=r},function(t,e){var n=Math.PI/180;t.exports=function(t){return n*t}},function(t,e){t.exports=parseInt},function(t,e){var n=180/Math.PI;t.exports=function(t){return n*t}},function(t,e){t.exports=function(t,e){return(t%e+e)%e}},function(t,e){t.exports=function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1e-5;return Math.abs(t-e)-1;)r.call(t,s,1);return t}},function(t,e,n){var r=n(46),i=n(84),o=Object.keys?function(t){return Object.keys(t)}:function(t){var e=[];return r(t,(function(n,r){i(t)&&"prototype"===r||e.push(r)})),e};t.exports=o},function(t,e,n){var r=n(677),i=n(237);t.exports=function(t,e){var n=r(e),o=n.length;if(i(t))return!o;for(var a=0;a]*>/,o={tr:document.createElement("tbody"),tbody:n,thead:n,tfoot:n,td:r,th:r,"*":document.createElement("div")};t.exports=function(t){var e=i.test(t)&&RegExp.$1;e in o||(e="*");var n=o[e];t=t.replace(/(^\s*)|(\s*$)/g,""),n.innerHTML=""+t;var r=n.childNodes[0];return n.removeChild(r),r}},function(t,e,n){var r=n(257),i={isBetween:function(t,e,n){return t>=e&&t<=n},getLineIntersect:function(t,e,n,r){var o=n.x-t.x,a=n.y-t.y,s=e.x-t.x,c=e.y-t.y,u=r.x-n.x,h=r.y-n.y,l=s*h-c*u,d=null;if(l*l>.001*(s*s+c*c)*(u*u+h*h)){var f=(o*h-a*u)/l,g=(o*c-a*s)/l;i.isBetween(f,0,1)&&i.isBetween(g,0,1)&&(d={x:t.x+f*s,y:t.y+f*c})}return d},getIntersectPointRect:function(t,e){var n=t.minX,r=t.minY,o=t.maxX-t.minX,a=t.maxY-t.minY,s=[],c={x:n+o/2,y:r+a/2};s.push({x:n,y:r}),s.push({x:n+o,y:r}),s.push({x:n+o,y:r+a}),s.push({x:n,y:r+a}),s.push({x:n,y:r});for(var u=null,h=1;hd&&(d=r);for(i=0;i0?(n=n.filter((function(t){var n=e[t.id];return!!n&&!1!==n.getShapeObj().bboxCalculation})),r.getChildrenBBox(n)):{minX:0,minY:0,maxX:this.get("width"),maxY:this.get("height")}},getFitViewPadding:function(){return r.toAllPadding(this.get("fitViewPadding"))},setFitView:function(t){if(!t)return this;if("autoZoom"===t)return this.autoZoom(),this;var e=this.getFitViewPadding(),n=this.get("width"),i=this.get("height"),o=this.getBBox(),a=o.maxX-o.minX,s=o.maxY-o.minY,c={x:0,y:0,width:n,height:i},u=r.getNineBoxPosition(t,c,a,s,e),h=[1,0,0,0,1,0,0,0,1];r.mat3.translate(h,h,[-o.minX+u.x,-o.minY+u.y]),this.updateMatrix(h)},_getZoomRatio:function(t){var e=this.get("maxZoom"),n=this.get("minZoom");return te&&(t=e),t},autoZoom:function(t){var e=this;t||(t=this.getFitViewPadding());var n=this.get("width"),i=this.get("height"),o=this.getBBox(),a=r.getAutoZoomMatrix({minX:0,minY:0,maxX:n,maxY:i},o,t,(function(t){return e._getZoomRatio(t)}));this.updateMatrix(a)},getZoom:function(){return this.getMatrix()[0]},updateMatrix:function(t){var e=this.getMatrix(),n={updateMatrix:t,originMatrix:e},r=e[0]!==t[0];return this.emit("beforeviewportchange",n),r&&this.emit("beforezoom",n),this.setMatrix(t),r&&this.emit("afterzoom",n),this.emit("afterviewportchange",n),this.draw(),this},zoom:function(t,e){if(!r.isNumber(t)){e=this._getZoomRatio(e);var n=this.get("_rootGroup"),i=r.clone(n.getMatrix()),o=i[6]+i[0]*t.x-e*t.x,a=i[7]+i[0]*t.y-e*t.y;return i[6]=0,i[7]=0,i[0]=e,i[4]=e,r.mat3.translate(i,i,[o,a]),this.updateMatrix(i),this}var s=this.get("width"),c=this.get("height");this.zoomByDom({x:s/2,y:c/2},t)},zoomByDom:function(t,e){var n=this.getPoint(t);return this.zoom(n,e),this},translate:function(t,e){var n=this.get("_rootGroup").getMatrix();return r.mat3.translate(n,n,[t,e]),this.updateMatrix(n),this},translateByDom:function(t,e){var n=this.get("_rootGroup").getMatrix()[0];return this.translate(t/n,e/n),this},getMatrix:function(){return this.get("_rootGroup").getMatrix()},setMatrix:function(t){this.get("_rootGroup").setMatrix(t)},getPoint:function(t){return this.getPointByDom(t)},getPointByDom:function(t){var e=this.get("_rootGroup").getMatrix();return r.invertMatrix(t,e)},getPointByCanvas:function(t){var e=this.get("_canvas").get("pixelRatio");return this.getPoint({x:t.x/e,y:t.y/e})},getPointByClient:function(t){var e=this.get("_canvas").getPointByClient(t.x,t.y);return this.getPointByCanvas(e)},getDomPoint:function(t){var e=this.get("_rootGroup").getMatrix();return r.applyMatrix(t,e)},getCanvasPoint:function(t){var e=this.get("_canvas").get("pixelRatio"),n=this.getDomPoint(t);return{x:n.x*e,y:n.y*e}},getClientPoint:function(t){var e=this.getCanvasPoint(t),n=this.get("_canvas").getClientByPoint(e.x,e.y);return{x:n.clientX,y:n.clientY}},focus:function(t){if(r.isString(t)&&(t=this.find(t)),t){var e=t.getCenter();this.focusPoint(e)}return this},focusPoint:function(t){var e=this.get("_rootGroup").getMatrix(),n=this.get("width"),r=this.get("height"),i=-e[6]+n/2-e[0]*t.x,o=-e[7]+r/2-e[0]*t.y;return this.translate(i,o),this},focusPointByDom:function(t){var e=this.getPoint(t);return this.focusPoint(e),this}},t.exports=i},function(t,e){t.exports={INIT:"_initDraw",AUGMENT:{_initDraw:function(){var t=this,e=this.get("_controllers").animate;["clear","show","hide","change","updatenodeposition"].forEach((function(n){e&&t.on("before"+n,(function(n){var r=t.get("_forcePreventAnimate"),i=n?n.affectedItemIds:void 0;!0!==r&&e&&e.cacheGraph("startCache",i)})),t.on("after"+n,(function(n){var r=n?n.affectedItemIds:void 0,i=t.get("_forcePreventAnimate");if(n&&"changeData"===n.action){var o=t.get("fitView");o&&t.setFitView(o)}!0!==i&&e?(e.cacheGraph("endCache",r),e.run()):t.draw()}))}))},draw:function(){this.get("_canvas").draw()},animateDraw:function(){this.get("_controllers").animate.run()}}}},function(t,e,n){var r=n(247);function i(t,e){var n=t.getGraphicGroup(),i=t.getBBox(),o=(i.minX+i.maxX)/2,a=(i.minY+i.maxY)/2,s=n.getMatrix()[0];n.transform([["t",-o,-a],["s",.01/s,.01/s],["t",o,a]]),n.animate({transform:[["t",-o,-a],["s",100*s,100*s],["t",o,a]]},r.enterDuration,r.enterEasing,e)}function o(t,e){var n=t.getGraphicGroup(),i=t.getBBox(),o=(i.minX+i.maxX)/2,a=(i.minY+i.maxY)/2,s=n.getMatrix()[0];n.animate({transform:[["t",-o,-a],["s",.01/s,.01/s],["t",o,a]]},r.leaveDuration,r.leaveEasing,e)}function a(t,e){t.deepEach((function(t){if(t.isShape){var n=t.attr("fillOpacity"),i=t.attr("strokeOpacity");t.attr({fillOpacity:0,strokeOpacity:0}),t.animate({fillOpacity:n,strokeOpacity:i},r.enterDuration,r.enterEasing,e)}}))}function s(t,e){t.deepEach((function(t){var n=t.attr("fillOpacity"),i=t.attr("strokeOpacity");t.isShape&&t.animate({fillOpacity:0,strokeOpacity:0},r.leaveDuration,r.leaveEasing,(function(){t.attr({fillOpacity:n,strokeOpacity:i}),e()}))}))}t.exports={enterScaleIn:function(t){var e=t.item;t.element.isItemContainer&&e.getKeyShape()&&i(e)},showScaleIn:function(t){var e=t.item;t.element.isItemContainer&&e.getKeyShape()&&i(e)},leaveScaleOut:function(t){var e=t.item,n=t.element,r=t.done;n.isItemContainer&&o(e,(function(){r()}))},hideScaleOut:function(t){var e=t.item,n=t.element,r=t.done;n.isItemContainer&&o(e,(function(){r()}))},enterFadeIn:function(t){var e=t.element,n=t.item;e.isItemContainer&&n.getKeyShape()&&a(e)},showFadeIn:function(t){var e=t.element,n=t.item;e.isItemContainer&&n.getKeyShape()&&a(e)},leaveFadeOut:function(t){var e=t.element,n=t.item,r=t.done;e.isItemContainer&&n.getKeyShape()&&s(e,r)},hideFadeOut:function(t){var e=t.element,n=t.item,r=t.done;e.isItemContainer&&n.getKeyShape()&&s(e,r)}}},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var i=n(599),o=n(693),a=n(26),s=n(247),c=["matrix","fillStyle","strokeStyle","endArrow","startArrow"],u=function(t){function e(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t);var n=e.prototype;return n.getDefaultCfg=function(){return{show:"scaleIn",hide:"scaleOut",enter:"scaleIn",leave:"scaleOut",update:function(t){var e=t.element,n=t.endKeyFrame.props;e.animate(function(t){for(var e=1;e0&&"changeData"===r&&!t.destroyed&&t.filter()}))},addFilter:function(t){return this.get("filters").push(t),t},removeFilter:function(t){var e=this.get("filters");this.set("filters",r.filter(e,t))},filter:function(){this.emit("beforefilter");var t=this.get("filters"),e=this.getItems(),n=this._getFilterItems();t.forEach((function(t){n=n.filter(t)})),e.forEach((function(t){-1===n.indexOf(t)?t.hide():t.show()})),this.draw(),this.emit("afterfilter")},_getFilterItems:function(){return this.getItems().filter((function(t){return!1!==t.getShapeObj().filter}))}},t.exports=i},function(t,e,n){var r=n(26),i=n(626),o={CFG:{modes:{default:[]},mode:"default",_eventCache:{}},INIT:"_initModes"};o.AUGMENT={_initModes:function(){var t=this.get("mode");this.changeMode(t)},changeMode:function(t){var e=this.get("modes");r.isEmpty(e)||r.isEmpty(e[t])||(i.resetMode(e[t],this),this.set("mode",t))},addBehaviour:function(t,e){var n=this.get("modes");e=e||this.get("mode"),r.isEmpty(n[e])&&(n[e]=[]);var o=n[e],a=[].concat(t);return r.each(a,(function(t){-1===o.indexOf(t)&&o.push(t)})),i.resetMode(n[e],this),this},removeBehaviour:function(t){var e=this.get("modes"),n=this.get("mode"),o=e[n];if(!r.isEmpty(o)){var a=[].concat(t);return o=o.filter((function(t){return-1===a.indexOf(t)})),e[n]=o,i.resetMode(o,this),this}},behaviourOn:function(t,e){var n=this._eventCache;n[t]||(n[t]=[]),n[t].push(e),this.on(t,e)},_off:function(){var t=this,e=this._eventCache;r.each(e,(function(e,n){r.each(e,(function(e){t.off(n,e)}))})),this._eventCache={}}},t.exports=o},function(t,e,n){var r=n(599),i=n(26),o={MOUSEMOVE:"mousemove",MOUSEDOWN:"mousedown",MOUSEUP:"mouseup",MOUSEENTER:"mouseenter",MOUSELEAVE:"mouseleave",CLICK:"click",DBLCLICK:"dblclick",DRAGSTART:"dragstart",DRAG:"drag",DRAGENTER:"dragenter",DRAGLEAVE:"dragleave",DRAGEND:"dragend",DROP:"drop",CONTEXTMENU:"contextmenu",WHEEL:"wheel",KEYDOWN:"keydown",KEYUP:"keyup",KEYPRESS:"keypress",MOUSEWHEEL:"mousewheel"},a=[o.DBLCLICK,o.MOUSEDOWN,o.MOUSEUP,o.MOUSEENTER,o.MOUSELEAVE,o.MOUSEMOVE,o.CONTEXTMENU,o.WHEEL,o.MOUSEWHEEL],s=[o.KEYDOWN,o.KEYUP,o.KEYPRESS],c=function(t){function e(e){var n;return(n=t.call(this,e)||this)._domEvents=[],n._initEventStates(),n._registerEvents(),n}!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t);var n=e.prototype;return n._initEventStates=function(){this._pressing=!1,this._dragging=!1,this._currentEventObj={},this._dragEventObj={}},n._registerEvents=function(){this._registerMouseEvents(),this._registerKeyboardEvents()},n._registerKeyboardEvents=function(){var t=this.graph,e=t.getKeyboardEventWrapper(),n=this._domEvents,r=t.get("keyboardEnable");i.each(s,(function(o){n.push(i.addEventListener(e,o,(function(e){var n=!0;i.isFunction(r)&&(n=r()),n&&t.emit(o,{domEvent:e})})))}))},n._registerMouseEvents=function(){var t=this,e=this,n=this.graph.getMouseEventWrapper(),r=this._domEvents;i.each(a,(function(a){r.push(i.addEventListener(n,a,(function(n){if(n.type!==o.MOUSEENTER||!n.fromElement||n.fromElement.parentNode&&!function(t,e){for(var n=t.parentNode;n;){if("foreignObject"===n.tagName)return!0;n=n.parentNode}return!1}(n.fromElement)){var r=t._currentEventObj;t._oldEventObj=r,t._processEventObj(n);var a=t._currentEventObj;e._simulateEvents(n,r,a),-1!==[o.MOUSELEAVE,o.MOUSEENTER].indexOf(n.type)&&e._triggerEvent("canvas:"+n.type),e._triggerEvent(n.type),n.type===o.MOUSELEAVE&&(t._dragging&&e._triggerEvent(o.DRAGLEAVE,i.mix({},a,{item:null,shape:null,currentItem:t._dragEventObj.item,currentShape:t._dragEventObj.shape})),e._initEventStates())}})))}))},n.destroy=function(){var t=this._domEvents;i.each(t,(function(t){t&&t.remove()})),this._domEvents=null},n._triggerEvent=function(t,e){if(e||(e="mouseleave"===t?this._oldEventObj:this._currentEventObj),"mousedown"===t&&(e.button=this._button),e._type=t,this.emitGraphEvent(t,e),-1===["canvas:"+o.MOUSELEAVE,"canvas:"+o.MOUSEENTER].indexOf(t)){var n=e.shape&&e.shape.eventPreFix;if(-1!==[o.DRAGSTART,o.DRAG,o.DRAGEND].indexOf(t)&&(n=e.currentShape&&e.currentShape.eventPreFix),n){var r=n+":"+t;e._type=r,i.isBoolean(e._isItemChange)?e._isItemChange&&this.emitGraphEvent(r,e):this.emitGraphEvent(r,e)}}},n.emitGraphEvent=function(t,e){this.graph.emit(t,e)},n._getDistanceToPress=function(t){return Math.pow(t.clientX-this._pressX,2)+Math.pow(t.clientY-this._pressY,2)},n._simulateEvents=function(t,e,n){void 0===e&&(e={}),void 0===n&&(n={});var r=this._dragEventObj.item,a=this._dragEventObj.shape;switch(t.type){case o.MOUSEDOWN:this._pressing=!0,this._button=t.button,this._pressX=t.clientX,this._pressY=t.clientY;break;case o.MOUSEMOVE:if(this._dragging){if(this._triggerEvent(o.DRAG,i.mix({},n,{button:this._button,currentItem:r,currentShape:a})),e.shape!==n.shape){var s=this._isItemChange(e,n);e.shape&&this._triggerEvent(o.DRAGLEAVE,i.mix({},n,{button:this._button,item:e.item,shape:e.shape,toItem:n.item,toShape:n.shape,currentItem:r,currentShape:a,_isItemChange:s})),n.shape&&this._triggerEvent(o.DRAGENTER,i.mix({},n,{button:this._button,currentItem:r,currentShape:a,fromItem:e.item,fromShape:e.shape,_isItemChange:s}))}}else this._pressing&&this._getDistanceToPress(t)>9&&(this._dragging=!0,this._dragEventObj=e,r=this._dragEventObj.item,a=this._dragEventObj.shape,this._triggerEvent(o.DRAGSTART,i.mix({},e,{button:this._button,currentItem:r,currentShape:a})));if(e.shape!==n.shape){var c=this._isItemChange(e,n);e.shape&&this._triggerEvent(o.MOUSELEAVE,i.mix({},n,{item:e.item,shape:e.shape,toItem:n.item,toShape:n.shape,_isItemChange:c})),n.shape&&this._triggerEvent(o.MOUSEENTER,i.mix({},n,{fromtItem:e.item,fromShape:e.shape,_isItemChange:c}))}break;case o.MOUSEUP:!this._dragging&&this._pressing?this._triggerEvent(o.CLICK,i.mix({},n,{button:this._button})):(this._triggerEvent(o.DROP,i.mix({},n,{button:this._button,currentItem:r,currentShape:a})),this._triggerEvent(o.DRAGEND,i.mix({},n,{button:this._button,currentItem:r,currentShape:a}))),this._pressing=!1,this._dragging=!1,this._dragEventObj={};break;default:return}},n._isItemChange=function(t,e){var n=t.shape,r=e.shape,o=n&&r&&(n.get("isItemChange")||r.get("isItemChange"));return o?o(r,n):i.isObject(t.item)&&i.isObject(e.item)?t.item.id!==e.item.id:t.item!==e.item},n._processEventObj=function(t){var e=this.graph.get("_canvas"),n=this._getEventObj(t,e);this._currentEventObj=n},n._parsePoint=function(t,e){return this.graph.getPointByCanvas({x:t,y:e})},n._getEventObj=function(t,e){var n=this.graph,r=t.clientX,i=t.clientY,o=e.getPointByClient(r,i),a=this._parsePoint(o.x,o.y),s=e.getShape(o.x,o.y,t),c=n.getItemByShape(s),u=e.get("pixelRatio");return{item:c,shape:s,x:a.x,y:a.y,domX:o.x/u,domY:o.y/u,domEvent:t}},e}(r);t.exports=c},function(t,e,n){var r={},i=n(698);r.INIT="_initEvents",r.CFG={keyboardEnable:!0},r.AUGMENT={_initEvents:function(){this.get("_controllers").events=new i({graph:this})}},t.exports=r},function(t,e,n){var r=n(26),i={};i.AUGMENT={find:function(t){return this.get("_itemMap")[t]},getNodes:function(){return this.get("_itemMap")._nodes},getEdges:function(){return this.get("_itemMap")._edges},getGroups:function(){return this.get("_itemMap")._groups},getGuides:function(){return this.get("_itemMap")._guides},getItems:function(){var t=this.get("_itemMap"),e=[];return r.each(t,(function(t){t.type&&e.push(t)})),e},getItemByShape:function(t){return t?this.getItem(t.id):null},getItem:function(t){var e=this.get("_itemMap");return r.isObject(t)?t.destroyed&&(t=e[t.id]):t=e[t],t}},t.exports=i},function(t,e,n){var r=n(599),i=n(26),o=["color","shape","size","label","style"],a=function(t){function e(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t);var n=e.prototype;return n._init=function(){var t=this,e={};i.each(o,(function(n){e[n]={},t[n]=function(r){return e[n].input=r,t}})),this.channels=e},n.addChannels=function(t){var e=this.channels;i.each(t,(function(t,n){e[n]={input:t}}))},n.mapping=function(t){var e=this.channels;i.each(e,(function(e,n){i.isFunction(e.input)?t[n]=e.input(t):e.input&&(t[n]=e.input)}))},e}(r);t.exports=a},function(t,e,n){var r=n(701),i={INIT:"_initMapper"};i.AUGMENT={_initMapper:function(){var t=this.get("_controllers");t.nodeMapper=new r({graph:this}),t.edgeMapper=new r({graph:this}),t.groupMapper=new r({graph:this}),t.guideMapper=new r({graph:this})},node:function(t){var e=this._getController("nodeMapper");return t&&e.addChannels(t),e},edge:function(t){var e=this._getController("edgeMapper");return t&&e.addChannels(t),e},group:function(t){var e=this._getController("groupMapper");return t&&e.addChannels(t),this._getController("groupMapper")},guide:function(t){var e=this._getController("guideMapper");return t&&e.addChannels(t),this._getController("guideMapper")}},t.exports=i},function(t,e,n){var r=function(t){function e(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.__proto__=e}(e,t);var n=e.prototype;return n.getDefaultCfg=function(){return{graph:null,auto:!0,processor:null}},n._init=function(){var t=this,e=this.graph;e.on("afteritemdraw",(function(t){var e=t.item,n=e.getKeyShape(),r=e.getModel();if(e.isEdge)r.lineWidth=n.attr("lineWidth");else if(e.isNode||e.isGroup){var i=e.getBBox();r.width=i.width,r.height=i.height}})),e.on("afterchange",(function(n){var r=n.action;"once"===t.auto?"changeData"===r&&(e.destroyed||e.preventAnimate((function(){t.layout()}))):t.auto&&!e.destroyed&&e.preventAnimate((function(){t.layout()}))}))},n.changeLayout=function(t){this.processor=t,this.layout()},n.layout=function(){var t=this.graph,e=this.getLayoutProcessor();t.emit("beforelayout");var n=t.getNodes().filter((function(t){return t.isVisible()})).map((function(t){return t.getModel()})),r=t.getEdges().filter((function(t){return t.isVisible()})).map((function(t){return t.getModel()})),i=t.getGroups().filter((function(t){return t.isVisible()})).map((function(t){return t.getModel()}));t._executeLayout(e,n,r,i),t.updateNodePosition(),t.emit("afterlayout")},n.getLayoutProcessor=function(){return this.processor?this.processor:this.processer},e}(n(599));t.exports=r},function(t,e,n){var r=n(26),i=n(703),o={CFG:{layout:void 0},INIT:"_initLayout"};o.AUGMENT={_initLayout:function(){var t=this.get("_controllers"),e=this._getLayoutCfg();e&&(t.layout=new i(r.mix({graph:this},e)))},_getLayoutCfg:function(){var t=this.get("layout");return r.isPlainObject(t)?t:r.isFunction(t)||r.isObject(t)?{processor:t}:null},layout:function(){return this._getController("layout").layout(),this},updateNodePosition:function(t){var e=this.getGuides(),n=[],i=[];return this.emit("beforeupdatenodeposition"),t?(t.forEach((function(t){t.getEdges().forEach((function(t){i.push(t)}));var e=t.getParent();e&&n.push(e)})),i=r.uniq(i),n=r.uniq(n)):(t=this.getNodes(),n=this.getGroups(),i=this.getEdges()),t.forEach((function(t){t.layoutUpdate()})),n.forEach((function(t){t.layoutUpdate()})),i.forEach((function(t){t.layoutUpdate()})),e.forEach((function(t){t.layoutUpdate()})),this.emit("afterupdatenodeposition"),this},changeLayout:function(t){return this._getController("layout").changeLayout(t),this},getLayout:function(){return this._getController("layout").getLayoutProcessor()}},t.exports=o},function(t,e,n){!function(e){"use strict";var n=function(){return{escape:function(t){return t.replace(/([.*+?^${}()|\[\]\/\\])/g,"\\$1")},parseExtension:t,mimeType:function(e){var n=t(e).toLowerCase();return function(){var t="application/font-woff";return{woff:t,woff2:t,ttf:"application/font-truetype",eot:"application/vnd.ms-fontobject",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",tiff:"image/tiff",svg:"image/svg+xml"}}()[n]||""},dataAsUrl:function(t,e){return"data:"+e+";base64,"+t},isDataUrl:function(t){return-1!==t.search(/^(data:)/)},canvasToBlob:function(t){return t.toBlob?new Promise((function(e){t.toBlob(e)})):function(t){return new Promise((function(e){for(var n=window.atob(t.toDataURL().split(",")[1]),r=n.length,i=new Uint8Array(r),o=0;o'+t+""})).then((function(t){return''+t+""})).then((function(t){return"data:image/svg+xml;charset=utf-8,"+t}))}(r,e.width||n.width(t),e.height||n.height(t))}))}function h(t,e){return u(t,e).then(n.makeImage).then(n.delay(100)).then((function(r){var i=function(t){var r=document.createElement("canvas");if(r.width=e.width||n.width(t),r.height=e.height||n.height(t),e.bgcolor){var i=r.getContext("2d");i.fillStyle=e.bgcolor,i.fillRect(0,0,r.width,r.height)}return r}(t);return i.getContext("2d").drawImage(r,0,0),i}))}function l(t,e,r){return r||!e||e(t)?Promise.resolve(t).then((function(t){return t instanceof HTMLCanvasElement?n.makeImage(t.toDataURL()):t.cloneNode(!1)})).then((function(n){return i(t,n,e)})).then((function(e){return function(t,e){return e instanceof Element?Promise.resolve().then((function(){!function(t,e){t.cssText?e.cssText=t.cssText:function(t,e){n.asArray(t).forEach((function(n){e.setProperty(n,t.getPropertyValue(n),t.getPropertyPriority(n))}))}(t,e)}(window.getComputedStyle(t),e.style)})).then((function(){[":before",":after"].forEach((function(r){!function(r){var i=window.getComputedStyle(t,r),o=i.getPropertyValue("content");if(""!==o&&"none"!==o){var a=n.uid();e.className=e.className+" "+a;var s=document.createElement("style");s.appendChild(function(t,e,r){var i="."+t+":"+e,o=r.cssText?function(t){var e=t.getPropertyValue("content");return t.cssText+" content: "+e+";"}(r):function(t){return n.asArray(t).map((function(e){return e+": "+t.getPropertyValue(e)+(t.getPropertyPriority(e)?" !important":"")})).join("; ")+";"}(r);return document.createTextNode(i+"{"+o+"}")}(a,r,i)),e.appendChild(s)}}(r)}))})).then((function(){t instanceof HTMLTextAreaElement&&(e.innerHTML=t.value),t instanceof HTMLInputElement&&e.setAttribute("value",t.value)})).then((function(){e instanceof SVGElement&&(e.setAttribute("xmlns","http://www.w3.org/2000/svg"),e instanceof SVGRectElement&&["width","height"].forEach((function(t){var n=e.getAttribute(t);n&&e.style.setProperty(t,n)})))})).then((function(){return e})):e}(t,e)})):Promise.resolve();function i(t,e,r){var i=t.childNodes;return 0===i.length?Promise.resolve(e):function(t,e,n){var r=Promise.resolve();return e.forEach((function(e){r=r.then((function(){return l(e,n)})).then((function(e){e&&t.appendChild(e)}))})),r}(e,n.asArray(i),r).then((function(){return e}))}}function d(t){return i.resolveAll().then((function(e){var n=document.createElement("style");return t.appendChild(n),n.appendChild(document.createTextNode(e)),t}))}function f(t){return o.inlineAll(t).then((function(){return t}))}t.exports=c}()},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var i=n(26),o=n(248),a=n(705),s=function(){function t(t){this.options=function(t){for(var e=1;e
    ");r=new o.Canvas({containerDOM:a,width:e,height:n})}return r.drawCount||(r.drawCount=0),r},e.drawInner=function(t,e){var n=this.options.graph,r=n.getCanvas(),i=n.get("renderer"),o=t.drawCount;if("svg"===i){var s=[];r.deepEach((function(t){"dom"===t.get("type")&&s.push(t)})),s.length>0?s.forEach((function(n){var r=n.get("el");if(r){n.domImageOnload=!1;var i=n.attr("width"),c=n.attr("height");a.toPng(r,{width:i,height:c}).then((function(r){var i=new Image;i.src=r,i.onload=function(){if(o===t.drawCount-1){n.domImage=i,n.domImageOnload=!0;for(var r=0;r0&&v>0?b=Math.PI/2-w:m<0&&v<0?b=-Math.PI/2-w:m>=0&&v<0?b=-w-Math.PI/2:m<=0&&v>0&&(b=Math.PI/2-w),p.rotate(b),p.translate(g.x,g.y),"end"===e?(d[d.length-1]=y[1]+g.y,d[d.length-2]=y[0]+g.x):(f[f.length-1]=y[1]+g.y,f[f.length-2]=y[0]+g.x),h.attr("path",l),this[e+"Arrow"]=p}},n._getControlPoints=function(){var t=this.model.controlPoints;return i.isArray(t)?t:[]},n._shouldDraw=function(){return t.prototype._shouldDraw.call(this)&&this.linkedItemVisible()},n._getPoint=function(t){if(t.isItem){var e=t.getBBox();return{x:e.centerX,y:e.centerY}}return{x:t.x,y:t.y}},n.linkedItemVisible=function(){var t=this.source,e=this.target;return i.isPlainObject(t)||i.isPlainObject(e)||t.isVisible()||e.isVisible()||t.collapsedParent!==e.collapsedParent},n.getSource=function(){var t=this.source,e=t.collapsedParent,n=this.itemMap;return e?n[e.id]:t},n.getTarget=function(){var t=this.target,e=t.collapsedParent,n=this.itemMap;return e?n[e.id]:t},n.getPoints=function(){var t=this.getSource(),e=this.getTarget(),n=this.model,r=this._getControlPoints(),o=this._getPoint(t),a=this._getPoint(e),s=[o].concat(r).concat([a]),c=s.length;if(t.isItem){var u=i.isNumber(this.model.sourceAnchor)&&t.id===n.source?this.model.sourceAnchor:s[1],h=t.getLinkPoints(u);s[0]=h[0]}if(e.isItem){var l=i.isNumber(this.model.targetAnchor)&&e.id===n.target?this.model.targetAnchor:s[c-2],d=e.getLinkPoints(l);s[c-1]=d[0]}return s},n.destroy=function(){var e=this.itemMap,n=this.model,r=e[n.source],o=e[n.target];r&&r.isItem&&i.Array.remove(r.edges,this),o&&o.isItem&&i.Array.remove(o.edges,this),t.prototype.destroy.call(this)},e}(n(608));t.exports=o},function(t,e,n){t.exports={Node:n(624),Edge:n(709),Group:n(708),Guide:n(707)}},function(t,e,n){var r; +/*! + * EventEmitter v5.2.5 - git.io/ee + * Unlicense - http://unlicense.org/ + * Oliver Caldwell - http://oli.me.uk/ + * @preserve + */!function(e){"use strict";function i(){}var o=i.prototype,a=e.EventEmitter;function s(t,e){for(var n=t.length;n--;)if(t[n].listener===e)return n;return-1}function c(t){return function(){return this[t].apply(this,arguments)}}o.getListeners=function(t){var e,n,r=this._getEvents();if(t instanceof RegExp)for(n in e={},r)r.hasOwnProperty(n)&&t.test(n)&&(e[n]=r[n]);else e=r[t]||(r[t]=[]);return e},o.flattenListeners=function(t){var e,n=[];for(e=0;e=0;n--)e[n].remove(t);return this._cfg.children=[],this}}),i.mixin(r.Group,[o]),t.exports=o},function(t,e,n){n(258).registerGuide("common",{draw:function(){console.warn("do not have this guide, please register one")}})},function(t,e,n){t.exports={common:n(716)}},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}var i=n(258),o=n(26),a=n(247);i.registerGroup("common",{draw:function(t){return t.getModel().collapsed?this.drawCollapsed(t):this.drawExpanded(t)},defaultWidth:184,defaultHeight:40,getLabel:function(t){return t.getModel().label},drawLabel:function(t,e,n){var r=this.getLabel(t);if(r){var i=t.getGraphicGroup(),s=[8,8];e+=s[0],n+=s[1];var c=t.getModel(),u=c.labelOffsetX,h=c.labelOffsetY,l=c.labelRotate;e=u?u+e:e,n=h?h+n:n;var d=o.mix(!0,{},a.labelStyle,{x:e,y:n,textAlign:"left",textBaseline:"top"});o.isObject(r)?o.mix(d,r):d.text=r;var f=i.addShape("text",{class:"label",attrs:d});if(l){var g=f.getBBox(),p=(g.maxX+g.minX)/2,m=(g.maxY+g.minY)/2;f.transform([["t",-p,-m],["r",l,l],["t",p,m]])}}},drawKeyShape:function(t,e){var n=e.x,r=e.y,i=e.width,s=e.height,c=t.getModel(),u=t.getGraphicGroup(),h=o.mix({},a.groupStyle,c.style),l=o.getRectPath(n,r,i,s,h.radius);return t.lastChildrenBox=e,u.addShape("path",{attrs:o.mix({},h,{path:l})})},getChildrenBBox:function(t){var e=function(t){for(var e=1;e0){var i=t.getChildrenBBox();e.x=i.minX-a.groupBackgroundPadding[3],e.y=i.minY-a.groupBackgroundPadding[0],e.width=i.maxX-i.minX+a.groupBackgroundPadding[3]+a.groupBackgroundPadding[1],e.height=i.maxY-i.minY+a.groupBackgroundPadding[0]+a.groupBackgroundPadding[2]}else e.width=this.defaultWidth,e.height=this.defaultHeight;return o.isNil(e.x)&&!o.isNil(n.x)&&(e.x=n.x),o.isNil(e.y)&&!o.isNil(n.y)&&(e.y=n.y),e},drawExpanded:function(t){var e=this.getChildrenBBox(t),n=this.drawKeyShape(t,e);return this.drawLabel(t,e.x,e.y),n},drawCollapsed:function(t){var e=this.getChildrenBBox(t);e.width=this.defaultWidth,e.height=this.defaultHeight;var n=this.drawKeyShape(t,e);return this.drawLabel(t,e.x,e.y),n},anchor:{intersectBox:"rect"}})},function(t,e,n){t.exports={common:n(718)}},function(t,e,n){function r(t){for(var e=1;e3?e:3)/3,r=4*e/3,i=4*e;return[["M",-n,r],["L",0,0],["L",-n,-r],["A",i,i,0,0,1,-n,r],["Z"]]},shorten:function(t){var e=t.getKeyShape().attr("lineWidth");return 3.1*(e>3?e:3)},style:function(t){var e=t.getKeyShape().attr();return{fillOpacity:e.strokeOpacity,fill:e.stroke}}};o.registerEdge("common",{draw:function(t){var e=this.drawKeyShape(t);return this.drawLabel(t,e),e},drawKeyShape:function(t){var e=t.getGraphicGroup(),n=this.getStyle(t),r=this.getPath(t);return e.addShape("path",{attrs:a.mix({},n,{path:r})})},getStyle:function(t){var e=t.getModel();return a.mix(!0,{},{stroke:e.color||"#A3B1BF",strokeOpacity:.92,lineAppendWidth:4,lineWidth:e.size||1},e.style)},getPath:function(t){var e=t.getPoints();return a.pointsToPolygon(e)},getLabel:function(t){return t.getModel().label},getDefaultLabelRectStyle:function(){return{fill:"white"}},getDefaultLabelRectPadding:function(){return a.toAllPadding([4,8])},drawLabel:function(t,e){var n=this.getLabel(t),r=t.getGraphicGroup(),i=t.getModel(),o=i.labelOffsetX,c=i.labelOffsetY,u=i.labelRotate;if(n){var h=e.getPoint(.5);if(!h)return;h.x=o?h.x+o:h.x,h.y=c?h.y+c:h.y;var l=a.mix(!0,{},s.labelStyle,h);a.isObject(n)?a.mix(l,n):l.text=n,n=r.addShape("text",{class:"label",attrs:l});var d=this.getDefaultLabelRectPadding(t),f=this.getDefaultLabelRectStyle(t),g=n.getBBox(),p=i.labelRectStyle?a.mix({},f,i.labelRectStyle):f,m=r.addShape("rect",{attrs:a.mix({},p,{x:g.minX-d[3],y:g.minY-d[0],width:g.maxX-g.minX+d[1]+d[3],height:g.maxY-g.minY+d[0]+d[2]})});if(u){var v=(g.maxX+g.minX)/2,x=(g.maxY+g.minY)/2;n.transform([["t",-v,-x],["r",u,u],["t",v,x]]),m.transform([["t",-v,-x],["r",u,u],["t",v,x]])}a.toFront(n)}},startArrow:r({},c,{tangent:function(t){return t.getKeyShape().getStartTangent()},ratio:function(){return 0}}),endArrow:r({},c,{tangent:function(t){return t.getKeyShape().getEndTangent()},ratio:function(){return 1}})})},function(t,e,n){t.exports={common:n(720)}},function(t,e,n){var r=n(258),i=n(26);r.registerNode("html",{getHtml:function(t){return t.getModel().html},cssSize:!0,draw:function(t){var e=i.createDOM('
    '),n=t.getGraphicGroup(),r=t.getGraph();if("svg"!==r.get("renderer"))throw new Error("please use svg renderer draw html element !");var o=r.getGraphContainer(),a=this.getSize(t),s=this.getStyle(t),c=this.cssSize,u=this.getHtml(t);u=i.createDOM(u),e.css({position:"absolute",padding:"0px",margin:"0px"}),e.appendChild(u),o.appendChild(e),c&&(a[0]=e.width(),a[1]=e.height());var h=-a[0]/2,l=-a[1]/2,d=a[0],f=a[1],g=n.addShape("rect",{attrs:i.mix({},s,{x:h,y:l,width:d,height:f})});return n.addShape("dom",{attrs:i.mix({x:h,y:l,width:d,height:f,html:e})}),g}})},function(t,e,n){var r=n(258),i=n(26),o=n(247);r.registerNode("common",{draw:function(t){var e=t.getGraphicGroup(),n=this.drawLabel(t),r=this.drawKeyShape(t);return n&&i.toFront(n,e),r},getSize:function(t){var e=t.getModel().size;return i.isArray(e)?e:i.isNumber(e)?[e,e]:[o.defaultNodeSize,o.defaultNodeSize]},getStyle:function(t){var e=t.getModel();return i.mix(!0,{lineWidth:1,fill:e.color||"#40a9ff",stroke:e.color||"#096dd9",fillOpacity:.92},e.style)},getLabel:function(t){return t.getModel().label},drawKeyShape:function(t){var e=t.getGraphicGroup(),n=this.getStyle(t),r=this.getPath(t);return e.addShape("path",{attrs:i.mix({},n,{path:r})})},drawLabel:function(t){var e=t.getGraphicGroup(),n=this.getLabel(t),r=t.getModel(),a=r.labelOffsetX,s=r.labelOffsetY,c=r.labelRotate;if(!i.isNil(n)){var u=i.mix(!0,{},o.labelStyle,{x:a||0,y:s||0});i.isObject(n)?i.mix(u,n):u.text=n;var h=e.addShape("text",{class:"label",attrs:u});return c&&h.rotate(c),h}},getPath:function(t){var e=this.getSize(t);return i.getEllipsePath(0,0,e[0]/2,e[1]/2)}})},function(t,e,n){t.exports={common:n(723),html:n(722)}},function(t,e,n){var r=n(682),i=n(257),o={getAutoZoomMatrix:function(t,e,n,i){var o=[1,0,0,0,1,0,0,0,1],a=t.maxX-t.minX,s=t.maxY-t.minY,c=(e.maxX+e.minX)/2,u=(e.maxY+e.minY)/2,h=a-n[1]-n[3],l=s-n[0]-n[2],d=e.maxX-e.minX,f=e.maxY-e.minY,g=Math.min(l/f,h/d);return i&&(g=i(g)),r.mat3.translate(o,o,[-c,-u]),r.mat3.scale(o,o,[g,g]),r.mat3.translate(o,o,[a/2,s/2]),o},getNineBoxPosition:function(t,e,n,r,i){var o={};switch(t){case"tl":o.y=e.x+i[0],o.x=e.y+i[3];break;case"lc":o.y=(e.height-r)/2,o.x=i[3];break;case"bl":o.y=e.height-r-i[2],o.x=i[3];break;case"cc":o.y=(e.height-r)/2,o.x=(e.width-n)/2;break;case"tc":o.y=i[0],o.x=(e.width-n)/2;break;case"tr":o.y=i[0],o.x=e.width-n-i[1];break;case"rc":o.y=(e.height-r)/2,o.x=e.width-n-i[1];break;case"br":o.y=e.height-r-i[2],o.x=e.width-n-i[1];break;case"bc":o.y=e.height-r-i[2],o.x=(e.width-n)/2;break;default:o.y=e.x+i[0],o.x=e.y+i[3]}return o.x+=e.x,o.y+=e.y,o},getTotalBBox:function(t){var e=1/0,n=-1/0,r=1/0,i=-1/0;return t.forEach((function(t){t.minXn&&(n=t.maxX),t.minYi&&(i=t.maxY)})),{minX:e,minY:r,maxX:n,maxY:i,width:n-e,height:i-r}},getChildrenBBox:function(t){var e=1/0,n=-1/0,r=1/0,a=-1/0;i.each(t,(function(t){var i=t.isGroup?o.getChildrenBBox(t.get("children")):t.getBBox();if(!i)return!0;var s=[i.minX,i.minY,1],c=[i.minX,i.maxY,1],u=[i.maxX,i.minY,1],h=[i.maxX,i.maxY,1];t.apply(s),t.apply(c),t.apply(u),t.apply(h);var l=Math.min(s[0],c[0],u[0],h[0]),d=Math.max(s[0],c[0],u[0],h[0]),f=Math.min(s[1],c[1],u[1],h[1]),g=Math.max(s[1],c[1],u[1],h[1]);ln&&(n=d),fa&&(a=g)}));var s={minX:e,minY:r,maxX:n,maxY:a};return s.x=s.minX,s.y=s.minY,s.width=s.maxX-s.minX,s.height=s.maxY-s.minY,s.centerX=(s.minX+s.maxX)/2,s.centerY=(s.minY+s.maxY)/2,s},getBBox:function(t,e){var n,i=t.getBBox(),o={x:i.minX,y:i.minY},a={x:i.maxX,y:i.maxY};if(e.isGroup){for(n=t;n!==e;){var s=n.getMatrix();o=r.applyMatrix(o,s),a=r.applyMatrix(a,s),n=n.getParent()}var c=n.getMatrix();o=r.applyMatrix(o,c),a=r.applyMatrix(a,c)}else o=r.applyMatrix(o,e),a=r.applyMatrix(a,e);return{minX:o.x,minY:o.y,maxX:a.x,maxY:a.y}},toBack:function(t){t.toBack()},toFront:function(t){t.toFront()}};t.exports=o},function(t,e,n){var r=n(257);t.exports={isNode:function(t){return t&&r.isObject(t)&&"node"===t.type},isEdge:function(t){return t&&r.isObject(t)&&"edge"===t.type},isGroup:function(t){return t&&r.isObject(t)&&"group"===t.type}}},function(t,e,n){var r=n(257),i={};r.mix(i,{addEventListener:function(t,e,n){return t.attachEvent?(t.attachEvent("on"+e,n),{remove:function(){t.detachEvent("on"+e,n)}}):t.addEventListener?(t.addEventListener(e,n,!1),{remove:function(){t.removeEventListener(e,n,!1)}}):void 0},createDOM:function(t,e){var n;return(n=r.isString(t)?r.createDom(t):t).bbox=n.getBoundingClientRect(),n.hide=function(){return n.style.visibility="hidden",n},n.show=function(){return n.style.visibility="visible",n},n.css=function(t){return r.modifyCSS(n,t),n},n.width=function(){return r.getWidth(n)},n.height=function(){return r.getHeight(n)},n.destroy=function(){n.parentNode&&n.parentNode.removeChild(n)},n.on=function(t,e){n.addEventListener(t,e)},n.off=function(t,e){n.removeEventListener(t,e)},n.css(e),n},initDOMContainer:function(t,e){if(!t)throw new Error("please set the container for the "+e+" !");return r.isString(t)&&(t=document.getElementById(t)),t}}),t.exports=i},function(t,e){var n={svg:"svg",circle:"circle",rect:"rect",text:"text",path:"path",foreignObject:"foreignObject",polygon:"polygon",ellipse:"ellipse",image:"image"};t.exports=function(t,e,r){var i=r.target||r.srcElement;if(!n[i.tagName]){for(var o=i.parentNode;o&&!n[o.tagName];)o=o.parentNode;i=o}return this._cfg.el===i?this:this.find((function(t){return t._cfg&&t._cfg.el===i}))}},function(t,e,n){var r=n(19),i=/^p\s*\(\s*([axyn])\s*\)\s*(.*)/i,o=function(){function t(t){var e=document.createElementNS("http://www.w3.org/2000/svg","pattern");e.setAttribute("patternUnits","userSpaceOnUse");var n=document.createElementNS("http://www.w3.org/2000/svg","image");e.appendChild(n);var o=r.uniqueId("pattern_");e.id=o,this.el=e,this.id=o,this.cfg=t;var a=i.exec(t)[2];n.setAttribute("href",a);var s=new Image;function c(){console.log(s.width,s.height),e.setAttribute("width",s.width),e.setAttribute("height",s.height)}return a.match(/^data:/i)||(s.crossOrigin="Anonymous"),s.src=a,s.complete?c():(s.onload=c,s.src=s.src),this}return t.prototype.match=function(t,e){return this.cfg===e},t}();t.exports=o},function(t,e,n){var r=n(19),i=function(){function t(t){this.type="clip";var e=document.createElementNS("http://www.w3.org/2000/svg","clipPath");this.el=e,this.id=r.uniqueId("clip_"),e.id=this.id;var n=t._cfg.el;return e.appendChild(n.cloneNode(!0)),this.cfg=t,this}var e=t.prototype;return e.match=function(){return!1},e.remove=function(){var t=this.el;t.parentNode.removeChild(t)},t}();t.exports=i},function(t,e,n){var r=n(19),i=function(){function t(t,e){var n=document.createElementNS("http://www.w3.org/2000/svg","marker"),i=r.uniqueId("marker_");n.setAttribute("id",i);var o=document.createElementNS("http://www.w3.org/2000/svg","path");return o.setAttribute("stroke","none"),o.setAttribute("fill",t.stroke||"#000"),n.appendChild(o),n.setAttribute("overflow","visible"),n.setAttribute("orient","auto-start-reverse"),this.el=n,this.child=o,this.id=i,this.cfg=t["marker-start"===e?"startArrow":"endArrow"],this.stroke=t.stroke||"#000",!0===this.cfg?this._setDefaultPath(e,o):this._setMarker(t.lineWidth,o),this}var e=t.prototype;return e.match=function(){return!1},e._setDefaultPath=function(t,e){var n=this.el;e.setAttribute("d","M0,0 L6,3 L0,6 L3,3Z"),n.setAttribute("refX",3),n.setAttribute("refY",3)},e._setMarker=function(t,e){var n=this.el,i=this.cfg.path,o=this.cfg.d;r.isArray(i)&&(i=i.map((function(t){return t.join(" ")})).join("")),e.setAttribute("d",i),n.appendChild(e),o&&n.setAttribute("refX",o/t)},e.update=function(t){var e=this.child;e.attr?e.attr("fill",t):e.setAttribute("fill",t)},t}();t.exports=i},function(t,e,n){var r=n(19),i={shadowColor:"color",shadowOpacity:"opacity",shadowBlur:"blur",shadowOffsetX:"dx",shadowOffsetY:"dy"},o={x:"-40%",y:"-40%",width:"200%",height:"200%"},a=function(){function t(t){this.type="filter";var e=document.createElementNS("http://www.w3.org/2000/svg","filter");return r.each(o,(function(t,n){e.setAttribute(n,t)})),this.el=e,this.id=r.uniqueId("filter_"),this.el.id=this.id,this.cfg=t,this._parseShadow(t,e),this}var e=t.prototype;return e.match=function(t,e){if(this.type!==t)return!1;var n=!0,i=this.cfg;return r.each(Object.keys(i),(function(t){if(i[t]!==e[t])return n=!1,!1})),n},e.update=function(t,e){var n=this.cfg;return n[i[t]]=e,this._parseShadow(n,this.el),this},e._parseShadow=function(t,e){var n='';e.innerHTML=n},t}();t.exports=a},function(t,e,n){var r=n(19),i=/^l\s*\(\s*([\d.]+)\s*\)\s*(.*)/i,o=/^r\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)\s*(.*)/i,a=/[\d.]+:(#[^\s]+|[^\)]+\))/gi;function s(t){var e=t.match(a);if(!e)return"";var n="";return e.sort((function(t,e){return t=t.split(":"),e=e.split(":"),Number(t[0])-Number(e[0])})),r.each(e,(function(t){t=t.split(":"),n+=''})),n}var c=function(){function t(t){var e=null,n=r.uniqueId("gradient_");return"l"===t.toLowerCase()[0]?function(t,e){var n,o,a=i.exec(t),c=r.mod(r.toRadian(parseFloat(a[1])),2*Math.PI),u=a[2];c>=0&&c<.5*Math.PI?(n={x:0,y:0},o={x:1,y:1}):.5*Math.PI<=c&&c');return t.appendChild(n),this.type="svg",this.canvas=n,this.context=new a(n),this.toDraw=!1,this}var e=t.prototype;return e.draw=function(t){var e=this;e.animateHandler?e.toDraw=!0:function n(){e.animateHandler=r.requestAnimationFrame((function(){e.animateHandler=void 0,e.toDraw&&n()}));try{e._drawChildren(t)}catch(t){console.warn("error in draw canvas, detail as:"),console.warn(t),e.toDraw=!1}e.toDraw=!1}()},e.drawSync=function(t){this._drawChildren(t)},e._drawGroup=function(t,e){var n=t._cfg;n.removed||n.destroyed||(n.tobeRemoved&&(r.each(n.tobeRemoved,(function(t){t.parentNode&&t.parentNode.removeChild(t)})),n.tobeRemoved=[]),this._drawShape(t,e),n.children&&n.children.length>0&&this._drawChildren(t))},e._drawChildren=function(t){var e,n=t._cfg.children;if(n)for(var r=0;rs?1:0,d=Math.abs(c-s)>Math.PI?1:0,f=n.rs,g=n.re,p=e(s,n.rs,o),m=e(c,n.rs,o);n.rs>0?(a.push("M "+h.x+","+h.y),a.push("L "+m.x+","+m.y),a.push("A "+f+","+f+",0,"+d+","+(1===l?0:1)+","+p.x+","+p.y),a.push("L "+u.x+" "+u.y)):(a.push("M "+o.x+","+o.y),a.push("L "+u.x+","+u.y)),a.push("A "+g+","+g+",0,"+d+","+l+","+h.x+","+h.y),n.rs>0?a.push("L "+m.x+","+m.y):a.push("Z"),i.el.setAttribute("d",a.join(" "))},e._updateText=function(t){var e=t._attrs,n=t._cfg.attrs,r=t._cfg.el;for(var i in this._setFont(t),e)if(e[i]!==n[i]){if("text"===i){this._setText(t,""+e[i]);continue}if("fillStyle"===i||"strokeStyle"===i){this._setColor(t,i,e[i]);continue}if("matrix"===i){this._setTransform(t);continue}c[i]&&r.setAttribute(c[i],e[i])}t._cfg.attrs=Object.assign({},t._attrs),t._cfg.hasUpdate=!1},e._setFont=function(t){var e=t.get("el"),n=t._attrs,r=n.fontSize;e.setAttribute("alignment-baseline",u[n.textBaseline]||"baseline"),e.setAttribute("text-anchor",h[n.textAlign]||"left"),r&&+r<12&&(n.matrix=[1,0,0,0,1,0,0,0,1],t.transform([["t",-n.x,-n.y],["s",+r/12,+r/12],["t",n.x,n.y]]))},e._setText=function(t,e){var n=t._cfg.el,i=t._attrs.textBaseline||"bottom";if(e)if(~e.indexOf("\n")){var o=t._attrs.x,a=e.split("\n"),s=a.length-1,c="";r.each(a,(function(t,e){0===e?"alphabetic"===i?c+=''+t+"":"top"===i?c+=''+t+"":"middle"===i?c+=''+t+"":"bottom"===i?c+=''+t+"":"hanging"===i&&(c+=''+t+""):c+=''+t+""})),n.innerHTML=c}else n.innerHTML=e;else n.innerHTML=""},e._setClip=function(t,e){var n=t._cfg.el;if(e)if(n.hasAttribute("clip-path"))e._cfg.hasUpdate&&this._updateShape(e);else{this._createDom(e),this._updateShape(e);var r=this.context.addClip(e);n.setAttribute("clip-path","url(#"+r+")")}else n.removeAttribute("clip-path")},e._setColor=function(t,e,n){var r=t._cfg.el,i=this.context;if(n)if(n=n.trim(),/^[r,R,L,l]{1}[\s]*\(/.test(n)){var o=i.find("gradient",n);o||(o=i.addGradient(n)),r.setAttribute(c[e],"url(#"+o+")")}else if(/^[p,P]{1}[\s]*\(/.test(n)){var a=i.find("pattern",n);a||(a=i.addPattern(n)),r.setAttribute(c[e],"url(#"+a+")")}else r.setAttribute(c[e],n);else r.setAttribute(c[e],"none")},e._setShadow=function(t){var e=t._cfg.el,n=t._attrs,r={dx:n.shadowOffsetX,dy:n.shadowOffsetY,blur:n.shadowBlur,color:n.shadowColor};if(r.dx||r.dy||r.blur||r.color){var i=this.context.find("filter",r);i||(i=this.context.addShadow(r,this)),e.setAttribute("filter","url(#"+i+")")}else e.removeAttribute("filter")},t}();t.exports=l},function(t,e,n){t.exports={painter:n(735),getShape:n(728)}},function(t,e,n){var r=n(19),i=/[MLHVQTCSAZ]([^MLHVQTCSAZ]*)/gi,o=/[^\s\,]+/gi,a=/^l\s*\(\s*([\d.]+)\s*\)\s*(.*)/i,s=/^r\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)\s*(.*)/i,c=/^p\s*\(\s*([axyn])\s*\)\s*(.*)/i,u=/[\d.]+:(#[^\s]+|[^\)]+\))/gi;function h(t,e){var n=t.match(u);r.each(n,(function(t){t=t.split(":"),e.addColorStop(t[0],t[1])}))}t.exports={parsePath:function(t){return t=t||[],r.isArray(t)?t:r.isString(t)?(t=t.match(i),r.each(t,(function(e,n){if((e=e.match(o))[0].length>1){var i=e[0].charAt(0);e.splice(1,0,e[0].substr(1)),e[0]=i}r.each(e,(function(t,n){isNaN(t)||(e[n]=+t)})),t[n]=e})),t):void 0},parseStyle:function(t,e,n){if(r.isString(t)){if("("===t[1]||"("===t[2]){if("l"===t[0])return function(t,e,n){var i,o,s=a.exec(t),c=r.mod(r.toRadian(parseFloat(s[1])),2*Math.PI),u=s[2],l=e.getBBox();c>=0&&c<.5*Math.PI?(i={x:l.minX,y:l.minY},o={x:l.maxX,y:l.maxY}):.5*Math.PI<=c&&c');return t.appendChild(n),this.type="canvas",this.canvas=n,this.context=n.getContext("2d"),this.toDraw=!1,this}var e=t.prototype;return e.beforeDraw=function(){var t=this.canvas;this.context&&this.context.clearRect(0,0,t.width,t.height)},e.draw=function(t){var e=this;e.animateHandler?e.toDraw=!0:function n(){e.animateHandler=r.requestAnimationFrame((function(){e.animateHandler=void 0,e.toDraw&&n()})),e.beforeDraw();try{e._drawGroup(t)}catch(t){console.warn("error in draw canvas, detail as:"),console.warn(t),e.toDraw=!1}e.toDraw=!1}()},e.drawSync=function(t){this.beforeDraw(),this._drawGroup(t)},e._drawGroup=function(t){if(!t._cfg.removed&&!t._cfg.destroyed&&t._cfg.visible){var e=t._cfg.children,n=null;this.setContext(t);for(var r=0;r-1){var s=n[a];"fillStyle"===a&&(s=i.parseStyle(s,t,e)),"strokeStyle"===a&&(s=i.parseStyle(s,t,e)),"lineDash"===a&&e.setLineDash?r.isArray(s)?e.setLineDash(s):r.isString(s)&&e.setLineDash(s.split(" ")):e[a]=s}},t}();t.exports=a},function(t,e,n){t.exports={painter:n(738)}},function(t,e,n){t.exports={canvas:n(739),svg:n(736)}},function(t,e,n){var r=n(19),i=n(610),o=n(17),a=n(44),s=n(238),c=s.interpolate,u=s.interpolateArray,h=function(t){this._animators=[],this._current=0,this._timer=null,this.canvas=t};function l(t,e,n){var o,s=e.startTime;if(ng.length?(f=i.parsePathString(a[d]),g=i.parsePathString(s[d]),g=i.fillPathByDiff(g,f),g=i.formatPath(g,f),e.fromAttrs.path=g,e.toAttrs.path=f):e.pathFormatted||(f=i.parsePathString(a[d]),g=i.parsePathString(s[d]),g=i.formatPath(g,f),e.fromAttrs.path=g,e.toAttrs.path=f,e.pathFormatted=!0),o[d]=[];for(var p=0;p0){for(var a=r._animators.length-1;a>=0;a--)if((t=r._animators[a]).get("destroyed"))i.removeAnimator(a);else{if(!t.get("pause").isPaused)for(var s=(e=t.get("animators")).length-1;s>=0;s--)n=e[s],l(t,n,o)&&(e.splice(s,1),n.callback&&n.callback());0===e.length&&i.removeAnimator(a)}r.canvas.draw()}}))},addAnimator:function(t){this._animators.push(t)},removeAnimator:function(t){this._animators.splice(t,1)},isAnimating:function(){return!!this._animators.length},stop:function(){this._timer&&this._timer.stop()},stopAllAnimations:function(){this._animators.forEach((function(t){t.stopAnimate()})),this._animators=[],this.canvas.draw()},getTime:function(){return this._current}}),t.exports=h},function(t,e,n){var r=n(19),i=n(613),o={arc:n(602),ellipse:n(639),line:n(603)},a=r.createDom('').getContext("2d");function s(t,e,n){return n.createPath(a),a.isPointInPath(t,e)}var c={arc:function(t,e){var n=this._attrs,r=n.x,o=n.y,a=n.r,s=n.startAngle,c=n.endAngle,u=n.clockwise,h=this.getHitLineWidth();return!!this.hasStroke()&&i.arcline(r,o,a,s,c,u,h,t,e)},circle:function(t,e){var n=this._attrs,r=n.x,o=n.y,a=n.r,s=this.getHitLineWidth(),c=this.hasFill(),u=this.hasStroke();return c&&u?i.circle(r,o,a,t,e)||i.arcline(r,o,a,0,2*Math.PI,!1,s,t,e):c?i.circle(r,o,a,t,e):!!u&&i.arcline(r,o,a,0,2*Math.PI,!1,s,t,e)},dom:function(t,e){if(!this._cfg.el)return!1;var n=this._cfg.el.getBBox();return i.box(n.x,n.x+n.width,n.y,n.y+n.height,t,e)},ellipse:function(t,e){var n=this._attrs,o=this.hasFill(),a=this.hasStroke(),s=n.x,c=n.y,u=n.rx,h=n.ry,l=this.getHitLineWidth(),d=u>h?u:h,f=u>h?1:u/h,g=u>h?h/u:1,p=[t,e,1],m=[1,0,0,0,1,0,0,0,1];r.mat3.scale(m,m,[f,g]),r.mat3.translate(m,m,[s,c]);var v=r.mat3.invert([],m);return r.vec3.transformMat3(p,p,v),o&&a?i.circle(0,0,d,p[0],p[1])||i.arcline(0,0,d,0,2*Math.PI,!1,l,p[0],p[1]):o?i.circle(0,0,d,p[0],p[1]):!!a&&i.arcline(0,0,d,0,2*Math.PI,!1,l,p[0],p[1])},fan:function(t,e){var n=this,a=n.hasFill(),s=n.hasStroke(),c=n._attrs,u=c.x,h=c.y,l=c.rs,d=c.re,f=c.startAngle,g=c.endAngle,p=c.clockwise,m=[t-u,e-h],v=r.vec2.angleTo([1,0],m);function x(){var t=o.arc.nearAngle(v,f,g,p);if(r.isNumberEqual(v,t)){var e=r.vec2.squaredLength(m);if(l*l<=e&&e<=d*d)return!0}return!1}function y(){var r=n.getHitLineWidth(),o={x:Math.cos(f)*l+u,y:Math.sin(f)*l+h},a={x:Math.cos(f)*d+u,y:Math.sin(f)*d+h},s={x:Math.cos(g)*l+u,y:Math.sin(g)*l+h},c={x:Math.cos(g)*d+u,y:Math.sin(g)*d+h};return!!(i.line(o.x,o.y,a.x,a.y,r,t,e)||i.line(s.x,s.y,c.x,c.y,r,t,e)||i.arcline(u,h,l,f,g,p,r,t,e)||i.arcline(u,h,d,f,g,p,r,t,e))}return a&&s?x()||y():a?x():!!s&&y()},image:function(t,e){var n=this._attrs;if(this.get("toDraw")||!n.img)return!1;this._cfg.attrs&&this._cfg.attrs.img===n.img||this._setAttrImg();var r=n.x,o=n.y,a=n.width,s=n.height;return i.rect(r,o,a,s,t,e)},line:function(t,e){var n=this._attrs,r=n.x1,o=n.y1,a=n.x2,s=n.y2,c=this.getHitLineWidth();return!!this.hasStroke()&&i.line(r,o,a,s,c,t,e)},path:function(t,e){var n=this,i=n.get("segments"),o=n.hasFill(),a=n.hasStroke();function c(){if(!r.isEmpty(i)){for(var o=n.getHitLineWidth(),a=0,s=i.length;a=3&&a.push(r[0]),i.polyline(a,o,t,e)}return r&&o?s(t,e,n)||a():r?s(t,e,n):!!o&&a()},polyline:function(t,e){var n=this._attrs;if(this.hasStroke()){var r=n.points;if(r.length<2)return!1;var o=n.lineWidth;return i.polyline(r,o,t,e)}return!1},rect:function(t,e){var n=this,r=n.hasFill(),o=n.hasStroke();function a(){var r=n._attrs,o=r.x,a=r.y,s=r.width,c=r.height,u=r.radius,h=n.getHitLineWidth();if(0===u){var l=h/2;return i.line(o-l,a,o+s+l,a,h,t,e)||i.line(o+s,a-l,o+s,a+c+l,h,t,e)||i.line(o+s+l,a+c,o-l,a+c,h,t,e)||i.line(o,a+c+l,o,a-l,h,t,e)}return i.line(o+u,a,o+s-u,a,h,t,e)||i.line(o+s,a+u,o+s,a+c-u,h,t,e)||i.line(o+s-u,a+c,o+u,a+c,h,t,e)||i.line(o,a+c-u,o,a+u,h,t,e)||i.arcline(o+s-u,a+u,u,1.5*Math.PI,2*Math.PI,!1,h,t,e)||i.arcline(o+s-u,a+c-u,u,0,.5*Math.PI,!1,h,t,e)||i.arcline(o+u,a+c-u,u,.5*Math.PI,Math.PI,!1,h,t,e)||i.arcline(o+u,a+u,u,Math.PI,1.5*Math.PI,!1,h,t,e)}return r&&o?s(t,e,n)||a():r?s(t,e,n):!!o&&a()},text:function(t,e){var n=this.getBBox();if(this.hasFill()||this.hasStroke())return i.box(n.minX,n.maxX,n.minY,n.maxY,t,e)}};t.exports={isPointInPath:function(t,e){var n=c[this.type];return!!n&&n.call(this,t,e)}}},function(t,e,n){var r=n(58);r.Arc=n(638),r.Circle=n(637),r.Dom=n(636),r.Ellipse=n(635),r.Fan=n(634),r.Image=n(633),r.Line=n(632),r.Marker=n(611),r.Path=n(631),r.Polygon=n(630),r.Polyline=n(629),r.Rect=n(628),r.Text=n(627),t.exports=r},function(t,e,n){var r=n(19),i={delay:"delay",rotate:"rotate"},o={fill:"fill",stroke:"stroke",fillStyle:"fillStyle",strokeStyle:"strokeStyle"};t.exports={animate:function(t,e,n,a,s){void 0===s&&(s=0),this.set("animating",!0);var c=this.get("timeline");c||(c=this.get("canvas").get("timeline"),this.setSilent("timeline",c));var u=this.get("animators")||[];c._timer||c.initTimer(),r.isNumber(a)&&(s=a,a=null),r.isFunction(n)?(a=n,n="easeLinear"):n=n||"easeLinear";var h=function(t,e){var n={matrix:null,attrs:{}},a=e._attrs;for(var s in t)if("transform"===s)n.matrix=r.transform(e.getMatrix(),t[s]);else if("rotate"===s)n.matrix=r.transform(e.getMatrix(),[["r",t[s]]]);else if("matrix"===s)n.matrix=t[s];else{if(o[s]&&/^[r,R,L,l]{1}[\s]*\(/.test(t[s]))continue;i[s]||a[s]===t[s]||(n.attrs[s]=t[s])}return n}(t,this),l={fromAttrs:function(t,e){var n={},r=e._attrs;for(var i in t.attrs)n[i]=r[i];return n}(h,this),toAttrs:h.attrs,fromMatrix:r.clone(this.getMatrix()),toMatrix:h.matrix,duration:e,easing:n,callback:a,delay:s,startTime:c.getTime(),id:r.uniqueId()};u.length>0?u=function(t,e){var n=e.delay,i=Object.prototype.hasOwnProperty;return r.each(e.toAttrs,(function(e,o){r.each(t,(function(t){n2*Math.PI&&(t=t/180*Math.PI),this.transform([["t",-e,-n],["r",t],["t",e,n]])},move:function(t,e){var n=this.get("x")||0,r=this.get("y")||0;return this.translate(t-n,e-r),this.set("x",t),this.set("y",e),this},transform:function(t){var e=this,n=this._attrs.matrix;return r.each(t,(function(t){switch(t[0]){case"t":e.translate(t[1],t[2]);break;case"s":e.scale(t[1],t[2]);break;case"r":e.rotate(t[1]);break;case"m":e.attr("matrix",r.mat3.multiply([],n,t[1])),e.clearTotalMatrix()}})),e},setTransform:function(t){return this.attr("matrix",[1,0,0,0,1,0,0,0,1]),this.transform(t)},getMatrix:function(){return this.attr("matrix")},setMatrix:function(t){return this.attr("matrix",t),this.clearTotalMatrix(),this},apply:function(t,e){var n;return n=e?this._getMatrixByRoot(e):this.attr("matrix"),r.vec3.transformMat3(t,t,n),this},_getMatrixByRoot:function(t){t=t||this;for(var e=this,n=[];e!==t;)n.unshift(e),e=e.get("parent");n.unshift(e);var i=[1,0,0,0,1,0,0,0,1];return r.each(n,(function(t){r.mat3.multiply(i,t.attr("matrix"),i)})),i},getTotalMatrix:function(){var t=this._cfg.totalMatrix;if(!t){t=[1,0,0,0,1,0,0,0,1];var e=this._cfg.parent;e&&a(t,e.getTotalMatrix()),a(t,this.attr("matrix")),this._cfg.totalMatrix=t}return t},clearTotalMatrix:function(){},invert:function(t){var e=this.getTotalMatrix();if(o(e))t[0]/=e[0],t[1]/=e[4];else{var n=r.mat3.invert([],e);n&&r.vec3.transformMat3(t,t,n)}return this},resetTransform:function(t){var e=this.attr("matrix");i(e)||t.transform(e[0],e[1],e[3],e[4],e[6],e[7])}}},function(t,e,n){var r=n(19);t.exports={canFill:!1,canStroke:!1,initAttrs:function(t){return this._attrs={opacity:1,fillOpacity:1,strokeOpacity:1,matrix:[1,0,0,0,1,0,0,0,1]},this.attr(r.assign(this.getDefaultAttrs(),t)),this},getDefaultAttrs:function(){return{}},attr:function(t,e){if(0===arguments.length)return this._attrs;if(r.isObject(t)){for(var n in t)this._setAttr(n,t[n]);return this.clearBBox(),this._cfg.hasUpdate=!0,this}return 2===arguments.length?(this._setAttr(t,e),this.clearBBox(),this._cfg.hasUpdate=!0,this):this._attrs[t]},_setAttr:function(t,e){var n=this._attrs;n[t]=e,"fill"!==t&&"stroke"!==t?"opacity"!==t?"clip"===t&&e?this._setClip(e):"path"===t&&this._afterSetAttrPath?this._afterSetAttrPath(e):"transform"!==t?"rotate"===t&&this.rotateAtStart(e):this.transform(e):n.globalAlpha=e:n[t+"Style"]=e},clearBBox:function(){this.setSilent("box",null)},hasFill:function(){return this.canFill&&this._attrs.fillStyle},hasStroke:function(){return this.canStroke&&this._attrs.strokeStyle},_setClip:function(t){t._cfg.renderer=this._cfg.renderer,t._cfg.canvas=this._cfg.canvas,t._cfg.parent=this._cfg.parent,t.hasFill=function(){return!0}}}},function(t,e,n){var r=n(19),i=n(643),o=n(642),a=n(741),s=n(740),c=function t(e){t.superclass.constructor.call(this,e)};c.CFG={eventEnable:!0,width:null,height:null,widthCanvas:null,heightCanvas:null,widthStyle:null,heightStyle:null,containerDOM:null,canvasDOM:null,pixelRatio:null,renderer:"canvas"},r.extend(c,o),r.augment(c,{init:function(){c.superclass.init.call(this),this._setGlobalParam(),this._setContainer(),this._initPainter(),this._scale(),this.get("eventEnable")&&this._registEvents()},getEmitter:function(t,e){if(t){if(!r.isEmpty(t._getEvents()))return t;var n=t.get("parent");if(n&&!e.propagationStopped)return this.getEmitter(n,e)}},_getEventObj:function(t,e,n,r){var o=new i(t,e,!0,!0);return o.x=n.x,o.y=n.y,o.clientX=e.clientX,o.clientY=e.clientY,o.currentTarget=r,o.target=r,o},_triggerEvent:function(t,e){var n,r=this.getPointByClient(e.clientX,e.clientY),i=this.getShape(r.x,r.y,e),o=this.get("el");if("mousemove"===t){var a=this.get("preShape");if(a&&a!==i){var s=this._getEventObj("mouseleave",e,r,a);(n=this.getEmitter(a,e))&&n.emit("mouseleave",s),o.style.cursor="default"}if(i){var c=this._getEventObj("mousemove",e,r,i);if((n=this.getEmitter(i,e))&&n.emit("mousemove",c),a!==i){var u=this._getEventObj("mouseenter",e,r,i);n&&n.emit("mouseenter",u,e)}}else{var h=this._getEventObj("mousemove",e,r,this);this.emit("mousemove",h)}this.set("preShape",i)}else{var l=this._getEventObj(t,e,r,i||this);(n=this.getEmitter(i,e))&&n!==this&&n.emit(t,l),this.emit(t,l)}i&&!i.get("destroyed")&&(o.style.cursor=i.attr("cursor")||"default")},_registEvents:function(){var t=this,e=t.get("el");r.each(["mouseout","mouseover","mousemove","mousedown","mouseleave","mouseup","click","dblclick"],(function(n){e.addEventListener(n,(function(e){t._triggerEvent(n,e)}),!1)})),e.addEventListener("touchstart",(function(e){r.isEmpty(e.touches)||t._triggerEvent("touchstart",e.touches[0])}),!1),e.addEventListener("touchmove",(function(e){r.isEmpty(e.touches)||t._triggerEvent("touchmove",e.touches[0])}),!1),e.addEventListener("touchend",(function(e){r.isEmpty(e.changedTouches)||t._triggerEvent("touchend",e.changedTouches[0])}),!1)},_scale:function(){if("svg"!==this._cfg.renderType){var t=this.get("pixelRatio");this.scale(t,t)}},_setGlobalParam:function(){var t=this.get("renderer")||"canvas";"svg"===t?this.set("pixelRatio",1):this.get("pixelRatio")||this.set("pixelRatio",r.getRatio()),this._cfg.renderType=t;var e=s[t];this._cfg.renderer=e,this._cfg.canvas=this;var n=new a(this);this._cfg.timeline=n},_setContainer:function(){var t=this.get("containerId"),e=this.get("containerDOM");e||(e=document.getElementById(t),this.set("containerDOM",e)),r.modifyCSS(e,{position:"relative"})},_initPainter:function(){var t=this.get("containerDOM"),e=new this._cfg.renderer.painter(t);this._cfg.painter=e,this._cfg.canvasDOM=this._cfg.el=e.canvas,this.changeSize(this.get("width"),this.get("height"))},_resize:function(){var t=this.get("canvasDOM"),e=this.get("widthCanvas"),n=this.get("heightCanvas"),r=this.get("widthStyle"),i=this.get("heightStyle");t.style.width=r,t.style.height=i,t.setAttribute("width",e),t.setAttribute("height",n)},getWidth:function(){var t=this.get("pixelRatio");return this.get("width")*t},getHeight:function(){var t=this.get("pixelRatio");return this.get("height")*t},changeSize:function(t,e){var n=this.get("pixelRatio"),r=t*n,i=e*n;this.set("widthCanvas",r),this.set("heightCanvas",i),this.set("widthStyle",t+"px"),this.set("heightStyle",e+"px"),this.set("width",t),this.set("height",e),this._resize()},getPointByClient:function(t,e){var n=this.get("el"),r=this.get("pixelRatio")||1,i=n.getBoundingClientRect();return{x:(t-i.left)*r,y:(e-i.top)*r}},getClientByPoint:function(t,e){var n=this.get("el").getBoundingClientRect(),r=this.get("pixelRatio")||1;return{clientX:t/r+n.left,clientY:e/r+n.top}},draw:function(){this._cfg.painter.draw(this)},getShape:function(t,e,n){return 3===arguments.length&&this._cfg.renderer.getShape?this._cfg.renderer.getShape.call(this,t,e,n):c.superclass.getShape.call(this,t,e)},getRenderer:function(){return this._cfg.renderType},_drawSync:function(){this._cfg.painter.drawSync(this)},destroy:function(){var t=this._cfg,e=t.containerDOM,n=t.canvasDOM;n&&e&&e.removeChild(n),t.timeline.stop(),c.superclass.destroy.call(this)}}),t.exports=c},function(t,e,n){var r=n(248),i={};n(257).mix(i,r.PathUtil,{getRectPath:r.PathUtil.rectPath,pointsToPolygon:function(t){for(var e=[["M",t[0].x,t[0].y]],n=1;ne?(r&&(clearTimeout(r),r=null),s=u,a=t.apply(i,o),r||(i=o=null)):r||!1===n.trailing||(r=setTimeout(c,h)),a};return u.cancel=function(){clearTimeout(r),s=0,r=i=o=null},u}},function(t,e,n){var r=n(46),i=n(596),o=Object.prototype.hasOwnProperty;t.exports=function(t,e){if(null===t||!i(t))return{};var n={};return r(e,(function(e){o.call(t,e)&&(n[e]=t[e])})),n}},function(t,e,n){var r=n(46),i=n(118);t.exports=function(t,e){if(!i(t))return t;var n=[];return r(t,(function(t,r){n.push(e(t,r))})),n}},function(t,e,n){var r=n(84),i=n(614);t.exports=function(t,e,n){return r(n)?!!n(t,e):i(t,e)}},function(t,e,n){var r=n(118);t.exports=function(t,e){if(!r(t))return-1;var n=Array.prototype.indexOf;if(n)return n.call(t,e);for(var i=-1,o=0;o0)){t[o]=e[o];break}i=r(i,t[o-1],1)}t[o]=["Q"].concat(i.reduce((function(t,e){return t.concat(e)}),[]));break;case"T":t[o]=["T"].concat(i[0]);break;case"C":if(i.length<3){if(!(o>0)){t[o]=e[o];break}i=r(i,t[o-1],2)}t[o]=["C"].concat(i.reduce((function(t,e){return t.concat(e)}),[]));break;case"S":if(i.length<2){if(!(o>0)){t[o]=e[o];break}i=r(i,t[o-1],1)}t[o]=["S"].concat(i.reduce((function(t,e){return t.concat(e)}),[]));break;default:t[o]=e[o]}return t}},function(t,e,n){var r=n(46);t.exports=function(t,e){if(t.length!==e.length)return!1;var n=!0;return r(t,(function(t,r){if(t!==e[r])return n=!1,!1})),n}},function(t,e,n){var r=n(770);function i(t,e,n){var r=null,i=n;return e=0;f--)c=s[f].index,"add"===s[f].type?t.splice(c,0,[].concat(t[c])):t.splice(c,1)}if((o=t.length)=3&&(3===t.length&&e.push("Q"),e=e.concat(t[1])),2===t.length&&e.push("L"),e.concat(t[t.length-1])}))}(t,e,r));else{var o=[].concat(t);"M"===o[0]&&(o[0]="L");for(var a=0;a<=r-1;a++)i.push(o)}return i}t.exports=function(t,e){if(1===t.length)return t;var n=t.length-1,i=e.length-1,o=n/i,a=[];if(1===t.length&&"M"===t[0][0]){for(var s=0;s1?1:u<0?0:u)/2,l=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],d=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],f=0,g=0;g<12;g++){var p=h*l[g]+h,m=a(p,t,n,i,s),v=a(p,e,r,o,c),x=m*m+v*v;f+=d[g]*Math.sqrt(x)}return h*f},c=function(t,e,n,r,i,o,a,s){if(!(Math.max(t,n)Math.max(i,a)||Math.max(e,r)Math.max(o,s))){var c=(t-n)*(o-s)-(e-r)*(i-a);if(c){var u=((t*r-e*n)*(i-a)-(t-n)*(i*s-o*a))/c,h=((t*r-e*n)*(o-s)-(e-r)*(i*s-o*a))/c,l=+u.toFixed(2),d=+h.toFixed(2);if(!(l<+Math.min(t,n).toFixed(2)||l>+Math.max(t,n).toFixed(2)||l<+Math.min(i,a).toFixed(2)||l>+Math.max(i,a).toFixed(2)||d<+Math.min(e,r).toFixed(2)||d>+Math.max(e,r).toFixed(2)||d<+Math.min(o,s).toFixed(2)||d>+Math.max(o,s).toFixed(2)))return{x:u,y:h}}}},u=function(t,e,n){return e>=t.x&&e<=t.x+t.width&&n>=t.y&&n<=t.y+t.height},h=function(t,e,n,r){return null===t&&(t=e=n=r=0),null===e&&(e=t.y,n=t.width,r=t.height,t=t.x),{x:t,y:e,width:n,w:n,height:r,h:r,x2:t+n,y2:e+r,cx:t+n/2,cy:e+r/2,r1:Math.min(n,r)/2,r2:Math.max(n,r)/2,r0:Math.sqrt(n*n+r*r)/2,path:i(t,e,n,r),vb:[t,e,n,r].join(" ")}},l=function(t,e,n,i,o,a,s,c){r(t)||(t=[t,e,n,i,o,a,s,c]);var u=function(t,e,n,r,i,o,a,s){for(var c=[],u=[[],[]],h=void 0,l=void 0,d=void 0,f=void 0,g=0;g<2;++g)if(0===g?(l=6*t-12*n+6*i,h=-3*t+9*n-9*i+3*a,d=3*n-3*t):(l=6*e-12*r+6*o,h=-3*e+9*r-9*o+3*s,d=3*r-3*e),Math.abs(h)<1e-12){if(Math.abs(l)<1e-12)continue;(f=-d/l)>0&&f<1&&c.push(f)}else{var p=l*l-4*d*h,m=Math.sqrt(p);if(!(p<0)){var v=(-l+m)/(2*h);v>0&&v<1&&c.push(v);var x=(-l-m)/(2*h);x>0&&x<1&&c.push(x)}}for(var y=c.length,b=y,w=void 0;y--;)w=1-(f=c[y]),u[0][y]=w*w*w*t+3*w*w*f*n+3*w*f*f*i+f*f*f*a,u[1][y]=w*w*w*e+3*w*w*f*r+3*w*f*f*o+f*f*f*s;return u[0][b]=t,u[1][b]=e,u[0][b+1]=a,u[1][b+1]=s,u[0].length=u[1].length=b+2,{min:{x:Math.min.apply(0,u[0]),y:Math.min.apply(0,u[1])},max:{x:Math.max.apply(0,u[0]),y:Math.max.apply(0,u[1])}}}.apply(null,t);return h(u.min.x,u.min.y,u.max.x-u.min.x,u.max.y-u.min.y)},d=function(t,e,n,r,i,o,a,s,c){var u=1-c,h=Math.pow(u,3),l=Math.pow(u,2),d=c*c,f=d*c,g=t+2*c*(n-t)+d*(i-2*n+t),p=e+2*c*(r-e)+d*(o-2*r+e),m=n+2*c*(i-n)+d*(a-2*i+n),v=r+2*c*(o-r)+d*(s-2*o+r);return{x:h*t+3*l*c*n+3*u*c*c*i+f*a,y:h*e+3*l*c*r+3*u*c*c*o+f*s,m:{x:g,y:p},n:{x:m,y:v},start:{x:u*t+c*n,y:u*e+c*r},end:{x:u*i+c*a,y:u*o+c*s},alpha:90-180*Math.atan2(g-m,p-v)/Math.PI}},f=function(t,e,n){if(!function(t,e){return t=h(t),e=h(e),u(e,t.x,t.y)||u(e,t.x2,t.y)||u(e,t.x,t.y2)||u(e,t.x2,t.y2)||u(t,e.x,e.y)||u(t,e.x2,e.y)||u(t,e.x,e.y2)||u(t,e.x2,e.y2)||(t.xe.x||e.xt.x)&&(t.ye.y||e.yt.y)}(l(t),l(e)))return n?0:[];for(var r=~~(s.apply(0,t)/8),i=~~(s.apply(0,e)/8),o=[],a=[],f={},g=n?0:[],p=0;p=0&&C<=1&&O>=0&&O<=1&&(n?g++:g.push({x:P.x,y:P.y,t1:C,t2:O}))}}return g};t.exports=function(t,e){return function(t,e,n){t=o(t),e=o(e);for(var r=void 0,i=void 0,a=void 0,s=void 0,c=void 0,u=void 0,h=void 0,l=void 0,d=void 0,g=void 0,p=[],m=0,v=t.length;ma&&(n=t,a=s)})),n}}},function(t,e,n){var r=n(236);t.exports=function(t){return r(t)&&t>0}},function(t,e,n){var r=n(236);t.exports=function(t){return r(t)&&t%2!=0}},function(t,e,n){var r=n(236);t.exports=function(t){return r(t)&&t<0}},function(t,e,n){var r=n(236),i=Number.isInteger?Number.isInteger:function(t){return r(t)&&t%1==0};t.exports=i},function(t,e,n){var r=n(236);t.exports=function(t){return r(t)&&t%2==0}},function(t,e,n){var r=n(236);t.exports=function(t){return r(t)&&t%1!=0}},function(t,e){t.exports=function(t,e){var n=e.toString(),r=n.indexOf(".");if(-1===r)return Math.round(t);var i=n.substr(r+1).length;return i>20&&(i=20),parseFloat(t.toFixed(i))}},function(t,e,n){var r=n(673);t.exports={clamp:n(618),fixedBase:n(788),isDecimal:n(787),isEven:n(786),isInteger:n(785),isNegative:n(784),isNumberEqual:r,isOdd:n(783),isPositive:n(782),maxBy:n(781),minBy:n(780),mod:n(672),snapEqual:r,toDegree:n(671),toInt:n(670),toInteger:n(670),toRadian:n(669)}},function(t,e,n){var r=n(45);t.exports=function(t){var e=0,n=0,i=0,o=0;return r(t)?1===t.length?e=n=i=o=t[0]:2===t.length?(e=i=t[0],n=o=t[1]):3===t.length?(e=t[0],n=o=t[1],i=t[2]):(e=t[0],n=t[1],i=t[2],o=t[3]):e=n=i=o=t,{r1:e,r2:n,r3:i,r4:o}}},function(t,e,n){var r=n(45),i=n(595),o=n(46),a=/[MLHVQTCSAZ]([^MLHVQTCSAZ]*)/gi,s=/[^\s\,]+/gi;t.exports=function(t){return r(t=t||[])?t:i(t)?(t=t.match(a),o(t,(function(e,n){if((e=e.match(s))[0].length>1){var r=e[0].charAt(0);e.splice(1,0,e[0].substr(1)),e[0]=r}o(e,(function(t,n){isNaN(t)||(e[n]=+t)})),t[n]=e})),t):void 0}},function(t,e){var n={};t.exports=function(t){var e=n[t];if(!e){for(var r=t.toString(16),i=r.length;i<6;i++)r="0"+r;e="#"+r,n[t]=e}return e}},function(t,e,n){var r=n(792);t.exports={number2color:r,numberToColor:r,parsePath:n(791),parseRadius:n(790)}},function(t,e){t.exports=function(t,e){if(t["_wrap_"+e])return t["_wrap_"+e];var n=function(n){t[e](n)};return t["_wrap_"+e]=n,n}},function(t,e){t.exports=function(t,e){return t["_wrap_"+e]}},function(t,e,n){t.exports={getWrapBehavior:n(795),wrapBehavior:n(794)}},function(t,e,n){var r=n(237),i=n(45),o=n(46);t.exports=function(t,e){for(var n=[],a={},s=0;se[r])return 1;if(t[r]1&&void 0!==arguments[1]?arguments[1]:[];if(r(e))for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:[];return r(t,(function(t){return!i(e,t)}))}},function(t,e,n){t.exports={contains:n(605),difference:n(808),find:n(807),firstValue:n(806),flatten:n(805),flattenDeep:n(804),getRange:n(803),merge:n(802),pull:n(676),pullAt:n(675),reduce:n(801),remove:n(800),sortBy:n(799),union:n(798),uniq:n(674),valuesOfKey:n(797)}},function(t,e){t.exports=function(t,e){var n=this.getStyle(t,"width",e);return"auto"===n&&(n=t.offsetWidth),parseFloat(n)}},function(t,e,n){var r=n(237);t.exports=function(t,e,n){try{return window.getComputedStyle?window.getComputedStyle(t,null)[e]:t.currentStyle[e]}catch(t){return r(n)?null:n}}},function(t,e){t.exports=function(){return window.devicePixelRatio?window.devicePixelRatio:2}},function(t,e){t.exports=function(t,e){var n=this.getWidth(t,e),r=parseFloat(this.getStyle(t,"borderLeftWidth"))||0,i=parseFloat(this.getStyle(t,"paddingLeft"))||0,o=parseFloat(this.getStyle(t,"paddingRight"))||0;return n+r+(parseFloat(this.getStyle(t,"borderRightWidth"))||0)+i+o}},function(t,e){t.exports=function(t,e){var n=this.getHeight(t,e),r=parseFloat(this.getStyle(t,"borderTopWidth"))||0,i=parseFloat(this.getStyle(t,"paddingTop"))||0,o=parseFloat(this.getStyle(t,"paddingBottom"))||0;return n+r+(parseFloat(this.getStyle(t,"borderBottomWidth"))||0)+i+o}},function(t,e){t.exports=function(t,e){var n=this.getStyle(t,"height",e);return"auto"===n&&(n=t.offsetHeight),parseFloat(n)}},function(t,e){t.exports=function(t,e){if(t&&t.getBoundingClientRect){var n=t.getBoundingClientRect(),r=document.documentElement.clientTop,i=document.documentElement.clientLeft;return{top:n.top-r,bottom:n.bottom-r,left:n.left-i,right:n.right-i}}return e||null}},function(t,e){t.exports=function(t,e,n){if(t){if(t.addEventListener)return t.addEventListener(e,n,!1),{remove:function(){t.removeEventListener(e,n,!1)}};if(t.attachEvent)return t.attachEvent("on"+e,n),{remove:function(){t.detachEvent("on"+e,n)}}}}},function(t,e,n){t.exports={addEventListener:n(817),createDom:n(681),getBoundingClientRect:n(816),getHeight:n(815),getOuterHeight:n(814),getOuterWidth:n(813),getRatio:n(812),getStyle:n(811),getWidth:n(810),modifyCSS:n(680),requestAnimationFrame:n(679)}},function(t,e,n){var r=n(46),i=n(606),o=n(818),a=n(809),s=n(796),c=n(793),u=n(789),h=n(779),l=n(778),d=n(774),f=n(768),g=n(763),p={DOMUtil:o,DomUtil:o,MatrixUtil:h,PathUtil:d,arrayUtil:a,domUtil:o,eventUtil:s,formatUtil:c,mathUtil:u,matrixUtil:h,objectUtil:l,stringUtil:f,pathUtil:d,typeUtil:g,augment:n(651),clone:n(616),debounce:n(756),deepMix:n(615),each:r,extend:n(650),filter:n(620),group:n(755),groupBy:n(648),groupToMap:n(649),indexOf:n(754),isEmpty:n(647),isEqual:n(614),isEqualWith:n(753),map:n(752),mix:i,pick:n(751),throttle:n(750),toArray:n(604),toString:n(594),uniqueId:n(646)};r([o,a,s,c,u,h,l,d,f,g],(function(t){i(p,t)})),t.exports=p},function(t,e,n){var r=n(683),i=n(626),o=n(247),a=n(609),s=n(248),c={Graph:n(625),Tree:n(689),Util:n(26),Layouts:n(623),G:s,Plugins:{},Components:{},Global:o,Shape:r,registerNode:r.registerNode,registerEdge:r.registerEdge,registerGroup:r.registerGroup,registerGuide:r.registerGuide,registerBehaviour:i.registerBehaviour,version:a,track:function(t){o.track=t}};n(684),t.exports=c}])},function(t,e,n){const r=n(7),i=n(1),o=n(10),a=[];class s extends i{constructor(t){const e={showHotArea:!1,defaultData:{roots:[{label:"思维导图",children:[{label:"新建节点"},{label:"新建节点"},{label:"新建节点"}]}]},shortcut:{append:!0,appendChild:!0,collapseExpand:!0,selectAll:!0},labelEditable:!0,graph:{modes:{default:["clickNodeSelected","keydownMoveSelection","clickCanvasSelected","keydownEditLabel","panBlank","wheelChangeViewport","panMindNode","clickCollapsedButton","clickExpandedButton","hoverButton","hoverNodeActived","dblclickItemEditLabel"],readOnly:["clickNodeSelected","wheelChangeViewport","keydownMoveSelection","hoverNodeActived","panCanvas","clickExpandedButton","hoverButton","clickCanvasSelected"]},layout:new r.Layouts.Mindmap({direction:"H",getSubTreeSep:t=>t.children&&t.children.length>0?t.hierarchy<=2?8:2:0,getHGap:t=>1===t.hierarchy?8:2===t.hierarchy?24:18,getVGap:t=>1===t.hierarchy||2===t.hierarchy?24:2,getSide:t=>t.data.side?t.data.side:"right"}),mode:"default",defaultIntersectBox:"rect",nodeDefaultShape:"mind-base",edgeDefaultShape:"mind-edge",minZoom:.2,maxZoom:2},align:{item:!1},rootShape:"mind-root",firstSubShape:"mind-first-sub",secondSubShape:"mind-second-sub",subShape:"mind-base",nodeDefaultShape:"mind-base",graphConstructor:r.Tree,defaultSide:"right"},n={};o.each(a,t=>{o.mix(n,t.CFG)}),o.mix(!0,e,n,t),super(e),this.isMind=!0}_init(){super._init(),o.each(a,t=>{t.INIT&&this[t.INIT]()});const t=this.getGraph(),e=this.get("defaultData"),n=t.get("mode"),r=t.getRootGroup().addGroup();if(this.set("hotAreaGroup",r),t.edge().shape(e=>{if(t.find(e.target).getModel().isPlaceholder)return"mind-placeholder-edge"}),e&&this.read(e),"default"===n){if(e){const e=this.getRoot(),n=t.find(e.id);this.setSelected(n,!0)}}else if("readOnly"===n){const t=this.get("shortcut");t.append=!1,t.appendChild=!1,t.selectAll=!1,t.delete=!1}if(e){const t=this.getRoot();this.focus(t.id)}}bindEvent(){super.bindEvent();const t=this.get("_graph");t.on("keydown",t=>{t.domEvent.preventDefault()}),t.on("beforechange",t=>{"add"===t.action?this._beforeAdd(t):"changeData"===t.action&&this._beforeChangeData(t)}),t.on("aftersource",()=>{this._setHierarchy()}),t.on("afterchange",()=>{this._setHotArea()}),t.on("afteritemdraw",t=>{const e=t.item,n=e.getModel();if(e.isEdge){const t=e.getGraphicGroup();o.toBack(t,t.getParent()),t.setSilent("capture",!1)}n.parent||(e.isRoot=!0,e.deleteable=!1,e.collapseExpand=!1,e.appendable=!1,e.dragable=!1)}),this.on("beforedelete",t=>{const e=t.items[0],n=this._getBrothers(e),r=this._getNth(e);n[r-1]?this.setSelected(n[r-1].id,!0):n[r+1]?this.setSelected(n[r+1].id,!0):this.setSelected(e.getParent(),!0)}),this.on("afteritemselected",t=>{this._tryAdjustViewport(t.item)})}getRoot(){return this.getGraph().getSource().roots[0]}_setHierarchy(t){const e=this.getGraph(),n=this.get("firstSubShape"),r=this.get("secondSubShape"),i=this.get("defaultSide");if(t){const o=e.find(t.parent);if(o){const e=o.getModel();t.hierarchy=e.hierarchy+1,"mind-placeholder"!==t.shape&&(2===t.hierarchy&&(t.shape=n,t.side||(t.side=i)),3===t.hierarchy&&(t.shape=r))}}else(t=this.getRoot()).hierarchy=1;o.traverseTree(t,(t,e)=>{t.hierarchy=e.hierarchy+1,t.side||(t.side=i),e.side&&(t.side=e.side),2===t.hierarchy?t.shape=n:3===t.hierarchy&&(t.shape=r)},t=>t.children)}getFirstChildrenBySide(t){const e=this.getRoot(),n=[];return e.children.forEach(e=>{e.side===t&&n.push(e)}),n}_getNth(t){return this.getGraph().getNth(t)}_getBrothers(t){return t.getParent().getModel().children}_getMoveChildModel(t){if(t&&t.length>0){const e=t.length;return t[parseInt(e/2)]}}_getVerticalMoveItem(t,e,n){const r=this.getGraph().getNodes(),i=t.getBBox(),o=[i.minX,i.maxX],a=[];let s;for(let t=0;t0&&(a.sort((t,e)=>t.long-e.long),s=a[0].node),s}_arrowTopItem(t){const e=this._getBrothers(t),n=this._getNth(t);return e[n-1]?e[n-1]:this._getVerticalMoveItem(t,(t,e,n)=>t.centerY=t.minX,(t,e)=>e.centerY-t.centerY)}_arrowBottomItem(t){const e=this._getBrothers(t),n=this._getNth(t);return e[n+1]?e[n+1]:this._getVerticalMoveItem(t,(t,e,n)=>t.centerY>e.centerY&&n<=t.maxX&&n>=t.minX,(t,e)=>t.centerY-e.centerY)}_arrowLeftItem(t){const e=o.getMindNodeSide(t);if(t.isRoot){const t=this.getFirstChildrenBySide("left");return this._getMoveChildModel(t)}if("left"===e){const e=t.getModel().children;return this._getMoveChildModel(e)}return t.getParent()}_arrowRightItem(t){const e=o.getMindNodeSide(t);if(t.isRoot){const t=this.getFirstChildrenBySide("right");return this._getMoveChildModel(t)}if("right"===e){const e=t.getModel().children;return this._getMoveChildModel(e)}return t.getParent()}_moveItemSelection(t){const e=this.getGraph(),n=this.getSelected()[0];if(!n)return;const{domEvent:r}=t,i=o.getKeyboradKey(r);let a;"ArrowUp"!==i||n.isRoot?"ArrowDown"!==i||n.isRoot?"ArrowLeft"===i?a=this._arrowLeftItem(n):"ArrowRight"===i&&(a=this._arrowRightItem(n)):a=this._arrowBottomItem(n):a=this._arrowTopItem(n),a&&(a=e.find(a.id),a.isVisible()&&(this.clearSelected(),this.setSelected(a,!0)))}showLabelEditor(t){const{domEvent:e}=t,n=this.getSelected()[0],r=o.getKeyboradKey(e);if(n&&1===r.length&&!e.metaKey&&!e.ctrlKey){const t=this.get("labelTextArea");n&&(this.beginEditLabel(n),t.innerHTML=r,o.setEndOfContenteditable(t))}}_tryAdjustViewport(t){const e=this.get("_graph"),n=t.getBBox(),r={x:n.minX,y:n.minY},i={x:n.maxX,y:n.maxY},o=e.getDomPoint(r),a=e.getDomPoint(i),s=e.getWidth(),c=e.getHeight();o.x<0&&e.translate(16-o.x,0),o.y<0&&e.translate(0,16-o.y),a.x>s&&e.translate(s-a.x-16,0),a.y>c&&e.translate(0,c-a.y-16)}_beforeChangeData(t){const e=t.data.roots[0],n=this.get("rootShape");e.shape=n,this._setHierarchy(e)}_beforeAdd(t){const e=this.get("_graph"),{model:n}=t,r=e.find(n.parent);r.getModel().collapsed&&e.update(r,{collapsed:!1}),this._setHierarchy(n)}_drawHotAreaShape(){const t=this.get("_graph"),e=this.get("hotAreaGroup"),n=this.get("hotAreas");e.clear(),n.forEach(t=>{e.addShape("rect",{attrs:{x:t.minX,y:t.minY,width:t.maxX-t.minX,height:t.maxY-t.minY,fill:t.color,fillOpacity:.4},capture:!1})}),t.draw()}_setHotArea(){const t=[],e=this.get("_graph"),n=this.getRoot(),r="placeholder",i=this.get("showHotArea"),a=e.find(n.id).getBBox();t.push({minX:a.minX-90,minY:a.minY-60,maxX:(a.minX+a.maxX)/2,maxY:a.maxY+60,parent:n,current:n,id:n.id+"left"+r,nth:n.children.length,side:"left",color:"orange"}),t.push({minX:(a.minX+a.maxX)/2,minY:a.minY-60,maxX:a.maxX+90,maxY:a.maxY+60,parent:n,current:n,id:n.id+"right"+r,nth:n.children.length,side:"right",color:"pink"}),o.traverseTree(n,(n,i,o)=>{const a=e.find(n.id);if(n.isPlaceholder||!a||!a.isVisible())return;const s=function(t,e,n){const r=n.children;let i=t;if(!n.parent)for(;r[i]&&r[i].side!==e.side;)i++;for(;r[i]&&r[i].isPlaceholder;)i++;if(r[i]&&r[i].side===e.side)return r[i]}(o+1,n,i),c=function(t,e,n){const r=n.children;let i=t;if(!n.parent)for(;r[i]&&r[i].side!==e.side;)i--;for(;r[i]&&r[i].isPlaceholder;)i--;if(r[i]&&r[i].side===e.side)return r[i]}(o-1,n,i),u=e.find(n.id).getBBox(),h=i.children,l=2===n.hierarchy&&"right"===n.side,d=2===n.hierarchy&&"left"===n.side;if(c||t.push({minX:l?u.minX-90:u.minX,minY:(()=>{let t=c?u.minY:u.minY-16;if(h[o-1]&&h[o-1].isPlaceholder&&h[o-1].side===n.side){t=e.find(h[o-1].id).getBBox().minY}return t})(),maxX:d?u.maxX+90:u.maxX,maxY:(u.minY+u.maxY)/2,parent:i,id:(c?c.id:void 0)+n.id+i.id+r,side:n.side,color:"yellow",nth:o}),s){const a=e.find(s.id).getBBox();t.push({minX:"left"===n.side?Math.max(u.minX,a.minX):l?u.minX-90:u.minX,minY:(u.minY+u.maxY)/2,maxX:"right"===n.side?Math.min(u.maxX,a.maxX):d?u.maxX+90:u.maxX,maxY:(a.minY+a.maxY)/2,parent:i,id:n.id+(s?s.id:void 0)+i.id+r,side:n.side,color:"blue",nth:o+1})}else t.push({minX:l?u.minX-90:u.minX,minY:(u.minY+u.maxY)/2,maxX:d?u.maxX+90:u.maxX,maxY:(()=>{let t=u.maxY+16;if(h[o+1]&&h[o+1].isPlaceholder&&h[o+1].side===n.side){t=e.find(h[o+1].id).getBBox().maxY}return t})(),parent:i,id:n.id+void 0+i.id+r,color:"red",nth:o+1,addOrder:"push",side:n.side});if(!n.children||0===n.children.length||1===n.children.length&&n.children[0].isPlaceholder){const e=100,o=0;let a;a=n.x>i.x?{minX:u.maxX,minY:u.minY-o,maxX:u.maxX+e,maxY:u.maxY+o}:{minX:u.minX-e,minY:u.minY-o,maxX:u.minX,maxY:u.maxY+o},t.push({...a,parent:n,id:NaN+n.id+r,nth:0,color:"green",side:n.side,addOrder:"push"})}},t=>t.children),this.set("hotAreas",t),i&&this._drawHotAreaShape()}hideHotArea(){const t=this.get("_graph");this.get("hotAreaGroup").clear(),t.draw(),this.set("showHotArea",!1)}showHotArea(){this._drawHotAreaShape(),this.set("showHotArea",!0)}getHotArea(t){let e;return this.get("hotAreas").forEach(n=>{if(t.x>n.minX&&t.xn.minY&&t.y{const t=e.getNodes();i=t.filter(t=>t.getModel().collapsed).map(t=>t.getModel().id),i.forEach(t=>{e.update(t,{collapsed:!1})}),e.layout(),o=this.getSelected().map(t=>t.id),a=this.getSelected().map(t=>t.id),this.clearSelected(),this.clearActived()},afterTransform:()=>{i.forEach(t=>{e.update(t,{collapsed:!0})}),this.setSelected(o,!0),this.setActived(a,!0)},...t})}save(){const t=this.get("_graph").save();return t.roots.forEach(t=>{o.traverseTree(t,t=>{delete t.x,delete t.y,delete t.width,delete t.height,delete t.parent,delete t.nth,delete t.hierarchy,delete t.index,delete t.shape},t=>t.children,!0)}),t}}o.each(a,t=>{o.mix(s.prototype,t.AUGMENT)}),i.setRegister(s,"mind","page"),t.exports=s},function(t,e,n){const r=n(7).Util;t.exports=class{getDefaultCfg(){return{}}constructor(t){const e=this.getDefaultCfg();r.mix(!0,this,e,t),this.init()}init(){}destroy(){}}},function(t,e,n){const r={...n(4),getMindNodeSide(t){const e=t.getModel();if(e.side)return e.side;const n=t.getParent();return n?n.getModel().side?n.getModel().side:r.getMindNodeSide(n):void 0}};t.exports=r},function(t,e,n){const r=n(1),i=n(16),o=n(19);class a extends i{constructor(t){const e={graph:{modes:{default:["panBlank","hoverGroupActived","keydownCmdWheelZoom","clickEdgeSelected","clickNodeSelected","clickCanvasSelected","clickGroupSelected","hoverNodeActived","hoverEdgeActived","hoverButton","clickCollapsedButton","clickExpandedButton","wheelChangeViewport","keydownShiftMultiSelected","dragNodeAddToGroup","dragOutFromGroup","panItem","hoverEdgeControlPoint","dragEdgeControlPoint"],add:["dragPanelItemAddNode"],readOnly:["panCanvas"],move:["panCanvas"],multiSelect:["dragMultiSelect"]},mode:"default",defaultIntersectBox:"rect",nodeDefaultShape:"flow-base",edgeDefaultShape:"flow-smooth",groupDefaultShape:"flow-base"},linkNode:!1};o.mix(!0,e,{},t),super(e),this.isFlow=!0}}r.setRegister(a,"flow","diagram"),t.exports=a},function(t,e,n){const r=n(16),i=n(1),o=n(6);class a extends r{constructor(t){const e={graph:{modes:{default:["panBlank","keydownCmdWheelZoom","clickEdgeSelected","clickNodeSelected","clickCanvasSelected","hoverNodeActived","hoverEdgeActived","hoverButton","wheelChangeViewport","keydownShiftMultiSelected","panItem","hoverNodeShowArrowController","hoverEdgeControlPoint","dragEdgeControlPoint","bpmnMoveEdgeController"],add:["dragPanelItemAddNode","processAddEdge"],readOnly:["panCanvas"],move:["panCanvas"],multiSelect:["dragMultiSelect"]},mode:"default",defaultIntersectBox:"rect",nodeDefaultShape:"bpmn-base",edgeDefaultShape:"bpmn-base"},arrowController:{thickness:32,long:32,controllers:[]}};o.mix(!0,e,{},t),super(e),this.isBPMN=!0}_init(){super._init(),this._initArrowController()}_createArrowController(t,e){const n=this.get("arrowController").controllers,r=o.createDOM('
    ',{visibility:"hidden",width:t+"px",height:e+"px",position:"absolute"});return r.setAttribute("draggable","true"),o.addEventListener(r,"dragstart",()=>{const t=r.hoverNode,e={shape:"bpmn-base",source:t.id,anchorIndex:r.getAttribute("anchorIndex")},n=t.getBBox(),i=this.getDelegation([{isEdge:!0}]);this.setSignal("dragEdge",!0),this.beginAdd("edge",e),this.set("addEdgeConfig",{addModel:e,delegation:i,startPoint:{x:n.centerX,y:n.centerY},sourceItem:t}),this.hideArrowController()}),n.push(r),r}showArrowController(t){this.get("arrowController").controllers.forEach(e=>{e.show(),e.hoverNode=t})}hideArrowController(){this.get("arrowController").controllers.forEach(t=>{t.hide()})}_initArrowController(){const t=this.getGraph().getGraphContainer(),e=this.get("arrowController"),{thickness:n,long:r}=e,i=this._createArrowController(n,r),o=this._createArrowController(n,r),a=this._createArrowController(r,n),s=this._createArrowController(r,n);t.appendChild(i),t.appendChild(o),t.appendChild(a),t.appendChild(s),e.topArrow=i,e.bottomArrow=o,e.leftArrow=a,e.rightArrow=s}bindEvent(){super.bindEvent();const t=this.getGraph();t.on("beforepanitem",()=>{this.hideArrowController()}),t.on("afterviewportchange",()=>{this.hideArrowController()})}}i.setRegister(a,"bpmn","diagram"),t.exports=a},function(t,e,n){const r=n(7),i=n(1),o=n(2),a=n(65),s=n(66),c=n(68),u=n(72),h=[a,n(73),u,s,c,n(74)];class l extends i{constructor(t){const e={shortcut:{copy:!0,paste:!0,selectAll:!0,addGroup:!0,unGroup:!0},graph:{minZoom:.2,maxZoom:2},graphConstructor:r.Graph,noEndEdge:!0},n={};o.each(h,t=>{o.mix(n,t.CFG)}),o.mix(!0,e,n,t),super(e),this.isFlow=!0}_init(){super._init(),o.each(h,t=>{t.INIT&&this[t.INIT]()})}bindEvent(){super.bindEvent(),this.on("beforeitemactived",t=>{const e=t.item,n=this.get("_graph").getShapeObj(e).getActivedOutterStyle(e);e.isNode&&this.addOutterShape(e,n)}),this.on("beforeitemunactived",t=>{const e=t.item;(e.isNode||e.isGroup)&&this.clearOutterShape(e)}),this.on("beforeitemselected",t=>{const e=t.item,n=this.get("_graph").getShapeObj(e).getSelectedOutterStyle(e);(e.isNode||e.isGroup)&&this.addOutterShape(e,n)}),this.on("beforeitemunselected",t=>{const e=t.item;(e.isNode||e.isGroup)&&this.clearOutterShape(e)})}}o.each(h,t=>{o.mix(l.prototype,t.AUGMENT)}),i.setRegister(l,"diagram","page"),t.exports=l},function(t,e){t.exports={gridStyle:{stroke:"#A3B1BF",lineWidth:.5},cursor:{panningCanvas:"-webkit-grabbing",beforePanCanvas:"-webkit-grab"},wheelPanRatio:.3,alignLineStyle:{stroke:"#FA8C16",lineWidth:1},nodeDelegationStyle:{stroke:"#1890FF",fill:"#1890FF",fillOpacity:.08,lineDash:[4,4],radius:4,lineWidth:1},edgeDelegationStyle:{stroke:"#1890FF",lineDash:[4,4],lineWidth:1}}},function(t,e,n){const r=n(18);n(32),n(33),t.exports=r},function(t,e,n){const r=n(13);n(75),n(80),t.exports=r},function(t,e,n){const r=n(1),i=n(16),o=n(24);class a extends i{constructor(t){const e={graph:{modes:{default:["panBlank","hoverGroupActived","keydownCmdWheelZoom","clickEdgeSelected","clickNodeSelected","clickCanvasSelected","clickGroupSelected","hoverNodeActived","hoverEdgeActived","hoverButton","clickCollapsedButton","clickExpandedButton","wheelChangeViewport","keydownShiftMultiSelected","dragNodeAddToGroup","dragOutFromGroup","panItem","hoverEdgeControlPoint","dragEdgeControlPoint"],add:["dragPanelItemAddNode","processAddEdge"],readOnly:["panCanvas"],move:["panCanvas"],multiSelect:["dragMultiSelect"]},mode:"default",defaultIntersectBox:"circle",nodeDefaultShape:"koni-base",edgeDefaultShape:"koni-base",groupDefaultShape:"koni-base",minZoom:.5,maxZoom:2},orbit:{satellite:["forkAndLink"]},anchorLink:!1,noEndEdge:!1};o.mix(!0,e,{},t),super(e),this.isKoni=!0}bindEvent(){super.bindEvent();const t=this.getGraph();t.on("afterchange",({item:e,action:n,originModel:r,updateModel:i})=>{if(e&&e.isEdge)if("add"===n||"remove"===n){const t=e.getSource(),n=e.getTarget();o.getParallelEdges(t,n,!0).forEach(t=>{t.update()})}else if("update"===n){const e=t.find(r.source),n=t.find(r.target);let a=[];if(o.getParallelEdges(e,n,!0).forEach(t=>{t.update()}),o.isNil(i.target)&&!o.isNil(i.source)){const e=t.find(i.source);a=o.getParallelEdges(e,n,!0)}else if(!o.isNil(i.target)&&o.isNil(i.source)){const n=t.find(i.target);a=o.getParallelEdges(e,n,!0)}else if(!o.isNil(i.target)&&!o.isNil(i.source)){const e=t.find(i.source),n=t.find(i.target);a=o.getParallelEdges(e,n,!0)}a.forEach(t=>{t.update()})}"changeData"===n&&t.getEdges().forEach(t=>{t.update()}),t.draw()})}}r.setRegister(a,"koni","diagram"),t.exports=a},function(t,e,n){const r=n(6),i={};function o(t){return t.getCurrentPage().getSelected().length>0}function a(t){const e=t.getCurrentPage();this.snapShot=e.save(),this.selectedItems=e.getSelected().map(t=>t.id),this.method&&(r.isString(this.method)?e[this.method]():this.method(t))}function s(t){const e=t.getCurrentPage();e.read(this.snapShot),e.setSelected(this.selectedItems,!0)}function c(t){return t.getCurrentPage().getMode()!==this.toMode}function u(t){const e=t.getCurrentPage();this.fromMode=e.getMode(),e.changeMode(this.toMode)}function h(t){t.getCurrentPage().changeMode(this.fromMode)}function l(t){const e={},n=[];return t.forEach(t=>{const i=t.model,o=r.mix(!0,{},{...i,id:r.guid()});e[i.id]=o.id,n.push({...t,model:o})}),n.forEach(t=>{const n=t.model;if(n.parent){const t=e[n.parent];t?n.parent=t:delete n.parent}}),n.sort((t,e)=>t.index-e.index),n}i.list=[],i.registerCommand=(t,e,n)=>{if(i[t])r.mix(i[t],e);else{let o={enable:()=>!0,init(){},execute:a,back:s,shortcutCodes:void 0,executeTimes:1,name:t,queue:!0,...e};n&&i[n]&&(o=r.mix({},i[n],e)),i[t]=o,i.list.push(o)}},i.execute=(t,e,n)=>{const o=r.mix(!0,{},i[t],n),a=e.get("_command");return o.enable(e)&&(o.init(),o.queue&&(a.queue.splice(a.current,a.queue.length-a.current,o),a.current+=1),e.emit("beforecommandexecute",{command:o}),o.execute(e),e.emit("aftercommandexecute",{command:o}),e.setCommandDOMenable()),o},i.enable=(t,e)=>i[t].enable(e),i.registerCommand("common",{back:s}),i.registerCommand("copyAdjacent",{enable(){return this.copyNode&&this.copyNode.isNode&&this.x&&this.y},execute(t){const{copyNode:e}=this,n=t.getCurrentPage(),i=n.getGraph(),o=e.getModel(),a=r.clone(o);a.id=r.guid(),a.x=this.x,a.y=this.y;const s=i.add("node",a),c=i.add("edge",{source:o.id,target:a.id});1===this.executeTimes&&(this.addIds=[s.id,c.id],this.page=n)},back(){const t=this.page.getGraph();this.addIds.forEach(e=>{t.remove(e)})}}),i.registerCommand("add",{enable(){return this.type&&this.addModel},execute(t){const e=t.getCurrentPage(),n=e.getGraph().add(this.type,this.addModel);1===this.executeTimes&&(this.addId=n.id,this.page=e)},back(){this.page.getGraph().remove(this.addId)}}),i.registerCommand("update",{enable(){return this.itemId&&this.updateModel},execute(t){const e=t.getCurrentPage(),n=e.getGraph(),i=n.find(this.itemId);1===this.executeTimes&&(this.originModel=r.getContrast(i.getModel(),this.updateModel),this.page=e),n.update(i,this.updateModel)},back(){const t=this.page.getGraph(),e=t.find(this.itemId);t.update(e,this.originModel)}}),i.registerCommand("redo",{queue:!1,enable(t){const e=t.get("_command"),n=e.queue;return e.currentt.get("_command").current>0,execute(t){const e=t.get("_command"),n=e.queue[e.current-1];n.executeTimes++,n.back(t),e.current-=1},shortcutCodes:[["metaKey","z"],["ctrlKey","z"]]}),i.registerCommand("copy",{queue:!1,enable:o,method(t){const e=t.getCurrentPage().getSelected(),n=t.get("_command"),i=e.map(t=>t.getGraphicGroup()),o=r.getChildrenBBox(i);n.clipboard=[],e.forEach(t=>{r.traverseTree(t,t=>{const e=t.getModel(),i=t.getGraphicGroup();n.clipboard.push({type:t.type,index:r.getIndex(i),model:e})},t=>t.getChildren&&t.getChildren(),!0)}),t.set("copyCenterBox",{...o})}}),i.registerCommand("pasteHere",{enable:t=>t.get("_command").clipboard.length>0,method(t){const e=t.getCurrentPage(),n=t.get("_command"),i=this.pasteData?this.pasteData:l(n.clipboard),o=this.copyCenterBox?this.copyCenterBox:t.get("copyCenterBox"),a=this.contextmenuPoint?this.contextmenuPoint:t.get("contextmenuPoint");e.clearSelected(),this.copyCenterBox=r.clone(o),this.pasteData=r.clone(i),this.contextmenuPoint=r.clone(a),i.forEach(t=>{const n=t.model;n.x&&(n.x+=a.x-o.minX),n.y&&(n.y+=a.y-o.minY),e.add(t.type,n)})},back:s}),i.registerCommand("paste",{enable:t=>t.get("_command").clipboard.length>0,method(t){const e=t.getCurrentPage(),n=t.get("_command"),i=this.pasteData?this.pasteData:l(n.clipboard);e.clearSelected(),this.pasteData=r.clone(i),i.forEach(t=>{const n=t.model;n.x&&(n.x+=10),n.y&&(n.y+=10),e.add(t.type,n)})},back:s}),i.registerCommand("addGroup",{init(){this.addGroupId=r.guid()},enable:t=>t.getCurrentPage().getSelected().length>1,method(t){t.getCurrentPage().addGroup({id:this.addGroupId})},back:s}),i.registerCommand("unGroup",{enable(t){const e=t.getCurrentPage().getSelected();return 1===e.length&&e[0].isGroup},method:"unGroup",back:s}),i.registerCommand("delete",{getDeleteItems(t){const e=t.getCurrentPage(),n=e.getGraph();let r=this.itemIds?this.itemIds.map(t=>n.find(t)):e.getSelected();return r=r.filter(t=>!1!==t.deleteable),r},enable(t){return this.getDeleteItems(t).length>0},method(t){const e=t.getCurrentPage(),n=this.getDeleteItems(t),i=e.getGraph();e.emit("beforedelete",{items:n}),r.each(n,t=>{i.remove(t)}),e.emit("afterdelete",{items:n}),this.itemIds=n.map(t=>t.getModel().id)},back:s,shortcutCodes:["Delete","Backspace"]}),i.registerCommand("selectAll",{method(t){const e=t.getCurrentPage();e.getGraph().getItems().forEach(t=>{e.setSelected(t,!0)})},back:s,shortcutCodes:[["metaKey","a"]]}),i.registerCommand("toBack",{enable:o,method:"toBack",back:s}),i.registerCommand("toFront",{enable:o,method:"toFront",back:s}),i.registerCommand("clear",{enable:t=>t.getCurrentPage().getItems().length>0,method:"clear",back:s}),i.registerCommand("multiSelect",{enable:c,toMode:"multiSelect",execute:u,back:h}),i.registerCommand("move",{enable:c,toMode:"move",execute:u,back:h}),t.exports=i},function(t,e,n){t.exports=n(2)},function(t,e,n){"use strict";n.r(e),n.d(e,"mouseEnterEdge",(function(){return i})),n.d(e,"mouseLeaveEdge",(function(){return o})),n.d(e,"startMove",(function(){return a})),n.d(e,"endMove",(function(){return s})),n.d(e,"mouseMoveEdge",(function(){return c})),n.d(e,"mergeLine",(function(){return u}));var r=n(0);function i({graph:t,bpmn:e,ev:n,backUpCursor:i}){const o=n.item,a=o.model.controlPoints,{index:s,vertical:c}=Object(r.e)({x:n.x,y:n.y},a);null!=s&&(i&&(o._cursor=e.getGraph().getMouseEventWrapper().style.cursor),t.update(o,{hold:{index:s,vertical:c}}),c?e.css({cursor:"col-resize"}):e.css({cursor:"row-resize"}))}function o({graph:t,bpmn:e,item:n}){n._cursor&&e.css({cursor:n._cursor}),delete n._cursor,t.update(n,{hold:void 0})}function a(t,e){const n=e.item;t.update(n,{lastMouse:{x:e.x,y:e.y}})}function s({graph:t,item:e}){t.update(e,{lastMouse:void 0,moveDelta:void 0})}function c(t,e,n){let r;r=e.model&&e.model.hold&&e.model.hold.vertical?{x:n.x-e.model.lastMouse.x,y:0}:{x:0,y:n.y-e.model.lastMouse.y},t.update(e,{edgeMoved:r,modifiedByMouse:!0})}function u(t,e,n){const r=t.model,i=e||r.hold.index,o=r.controlPoints;let a;null===n&&(n=r.hold.vertical);const s=[];return i>=2&&(n?Math.abs(o[i-2].x-o[i].x)<=3&&(a=[{x:o[i-2].x,y:o[i-2].y},{x:o[i-2].x,y:o[i+1].y}],s.push(i-1,i),r.hold.index=i-2,r.controlPoints[i+1].x=o[i-2].x):Math.abs(o[i-2].y-o[i].y)<=3&&(a=[{x:o[i-2].x,y:o[i-2].y},{x:o[i+1].x,y:o[i-2].y}],s.push(i-1,i),r.hold.index=i-2,r.controlPoints[i+1].y=o[i-2].y)),i<=o.length-4&&(n?Math.abs(o[i].x-o[i+2].x)<=3&&(a?(a[1]={x:o[i-2].x,y:o[i+3].y},r.controlPoints[i+3].x=o[i-2].x):(a=[{x:o[i+3].x,y:o[i].y},{x:o[i+3].x,y:o[i+3].y}],r.controlPoints[i].x=o[i+3].x),s.push(i+1,i+2)):Math.abs(o[i].y-o[i+2].y)<=3&&(a?(a[1]={x:o[i+3].x,y:o[i-2].y},r.controlPoints[i+3].y=o[i-2].y):(a=[{x:o[i].x,y:o[i+3].y},{x:o[i+3].x,y:o[i+3].y}],r.controlPoints[i].y=o[i+3].y),s.push(i+1,i+2))),s&&s.length&&o.splice(s[0],s.length),a}},function(t,e,n){const r=n(27),i=n(6);t.exports=class extends r{getDefaultCfg(){return{}}constructor(t){super();const e=this.getDefaultCfg();this._cfg=i.mix(!0,{},this._cfg,e,t)}get(t){return this._cfg[t]}set(t,e){this._cfg[t]=e}destroy(){this._cfg={},this.destroyed=!0}}},function(t,e,n){const r=n(4);t.exports={...r,getParallelEdges:(t,e,n=!1)=>t.getEdges().filter(t=>{const r=t.getModel();return r.target===e.id||n&&r.source===e.id}),object2array(t){const e=[];return r.each(t,t=>{e.push(t)}),e}}},function(t,e,n){const r=n(9),i=n(2);t.exports=class extends r{getDefaultCfg(){return{name:"",render:()=>"",bindEvent:()=>[]}}_renderDOM(){const t=i.createDOM(this.render(this.diagram));return this.dom=t,t.isSatellite=!0,t}getDOM(){return this.dom||this._renderDOM()}init(){const t=this.getDOM(),e=this.diagram.getGraph().getGraphContainer();t&&(t.css({position:"absolute",visibility:"hidden"}),e.appendChild(t),t&&this.bindEvent(t,this.diagram))}enable(){return!0}show(){this.getDOM().show()}hide(){this.getDOM().hide()}isVisible(){return"hidden"!==this.getDOM().style.visibility}destroy(){const{events:t}=this;t&&t.forEach(t=>{t.remove()})}}},function(t,e,n){t.exports=n(2)},function(t,e,n){const r=n(26),i=n(6),o=n(15);n(7);r.Editor=r,r.Util=i,r.Diagram=n(16),r.Page=n(1),r.Flow=n(95),r.Koni=n(102),r.Mind=n(107),r.Toolbar=n(121),r.Contextmenu=n(122),r.Command=n(15),r.BPMN=n(123),r.registerBehaviour=r.Page.registerBehaviour,r.registerNode=r.Page.registerNode,r.registerEdge=r.Page.registerEdge,r.registerGroup=r.Page.registerGroup,r.registerGuide=r.Page.registerGuide,r.registerCommand=o.registerCommand,t.exports=r},function(t,e,n){const r=n(21),i=n(6),o=n(15);t.exports=class extends r{getDefaultCfg(){return{_components:[],_command:{zoomDelta:.1,queue:[],current:0,clipboard:[]}}}_getComponentsBy(t){return this.get("_components").filter(t)}_bindCommands(t){i.each(t,t=>{const e=t.dataset,n=e.command;o[n]?i.addEventListener(t,"click",()=>{this.getCurrentPage().focusGraphWrapper(),this.executeCommand(n,e)}):console.warn("请设置有效的命令!")})}getCommands(){return this.get("_command").queue}getCurrentCommand(){const t=this.get("_command"),{queue:e,current:n}=t;return e[n-1]}executeCommand(t,e){i.isString(t)?o.execute(t,this,e):o.execute("common",this,{method:t},e)}commandEnable(t){return o.enable(t,this)}setCommandDOMenable(){const t=this.getComponentsByType("toolbar"),e=this.getComponentsByType("contextmenu"),n=[];t.forEach(t=>{i.each(t.getCommandDoms(),t=>{n.push(t)})}),e.forEach(t=>{i.each(t.getCommandDoms(),t=>{n.push(t)})}),i.each(n,t=>{const e=t.dataset.command;o.enable(e,this)?t.classList.remove("disable"):t.classList.add("disable")})}_onPageStatusChange(){this.setCommandDOMenable()}_afterAddContextmenu(){this.getCurrentPage()&&this.setCommandDOMenable()}_afterAddPage(t){this.setCommandDOMenable(),t.on("statuschange",t=>{this._onPageStatusChange(t)}),t.on("mousedown",()=>{this.getComponentsByType("contextmenu").forEach(t=>{t.hide()})}),t.on("contextmenu",e=>{const n=this.getComponentsByType("contextmenu"),r=t.getGraph().getCanvas().get("el"),o=i.getBoundingClientRect(r),a={x:e.x,y:e.y},{item:s}=e;s&&!s.isSelected&&(t.clearSelected(),t.setSelected(e.item,!0)),this.set("contextmenuPoint",a),e.domEvent.preventDefault(),n.forEach(t=>{t.show(),t.contextmenuPoint=a,t.move(o.left+e.domX,o.top+e.domY)})}),t.on("statuschange",t=>{this.getComponentsByType("contextmenu").forEach(e=>{e.switch(t.status)})}),this._bindShortcut(t)}_beforeAddToolbar(t){const e=t.getCommandDoms();this._bindCommands(e)}_beforeAddContextmenu(t){const e=t.getCommandDoms();t.bindEvent(),this._bindCommands(e)}getComponentsByType(t){return this._getComponentsBy(e=>e.type===t)}getCurrentPage(){const t=this.getComponentsByType("page");let e;return t.every(t=>!t.isActived||(e=t,!1)),e||(e=t[0]),e}getComponents(){return this.get("_components")}_shortcutEnable(t,e){const n=t.shortcutCodes,r=i.getKeyboradKey(e);let o=!1;for(let t=0;t{const n=this.getComponentsByType("contextmenu"),{domEvent:r}=t;n.forEach(t=>{t.hide()}),r.preventDefault();const i=o.list.filter(t=>t.shortcutCodes&&e[t.name]);for(let t=0;t{t.destroy()})}}},function(t,e,n){var r; +/*! + * EventEmitter v5.2.9 - git.io/ee + * Unlicense - http://unlicense.org/ + * Oliver Caldwell - https://oli.me.uk/ + * @preserve + */!function(e){"use strict";function i(){}var o=i.prototype,a=e.EventEmitter;function s(t,e){for(var n=t.length;n--;)if(t[n].listener===e)return n;return-1}function c(t){return function(){return this[t].apply(this,arguments)}}o.getListeners=function(t){var e,n,r=this._getEvents();if(t instanceof RegExp)for(n in e={},r)r.hasOwnProperty(n)&&t.test(n)&&(e[n]=r[n]);else e=r[t]||(r[t]=[]);return e},o.flattenListeners=function(t){var e,n=[];for(e=0;e"M9.75,9.60000014 L3.75,9.60000014 C3.33578644,9.60000014 3,9.2865995 3,8.90000022 C3,8.51340093 3.33578644,8.20000029 3.75,8.20000029 L9.75,8.20000029 C10.1642136,8.20000029 10.5,8.51340093 10.5,8.90000022 C10.5,9.2865995 10.1642136,9.60000014 9.75,9.60000014 M3,11.6999999 C3,11.3134006 3.33578644,11 3.75,11 L6.75,11 C7.16421356,11 7.5,11.3134006 7.5,11.6999999 C7.5,12.0865992 7.16421356,12.3999999 6.75,12.3999999 L3.75,12.3999999 C3.33578644,12.3999999 3,12.0865992 3,11.6999999 M3.75,13.7999997 L6.75,13.7999997 C7.16421356,13.7999997 7.5,14.1134004 7.5,14.4999996 C7.5,14.8865989 7.16421356,15.1999996 6.75,15.1999996 L3.75,15.1999996 C3.33578644,15.1999996 3,14.8865989 3,14.4999996 C3,14.1134004 3.33578644,13.7999997 3.75,13.7999997 M16.4985,4.00000072 L1.5015,4.00000072 C0.674533504,3.99922416 0.00289396564,4.6232696 0,5.39510058 L0,16.6048994 C0.00289396564,17.3767304 0.674533504,18.0007758 1.5015,17.9999993 L16.4985,17.9999993 C17.3254665,18.0007758 17.997106,17.3767304 18,16.6048994 L18,5.39510058 C17.997106,4.6232696 17.3254665,3.99922416 16.4985,4.00000072M19,13.9999993 L19,3 L5,3 L5,1.39510058 C5.00289397,0.623269599 5.6745335,-0.00077583787 6.5015,7.23978642e-07 L21.4985,7.23978642e-07 C22.3254665,-0.00077583787 22.997106,0.623269599 23,1.39510058 L23,12.6048994 C22.997106,13.3767304 22.3254665,14.0007758 21.4985,13.9999993 L19,13.9999993 Z",getCollapsedButtonPath:()=>r.getRectPath(0,0,14,14,2)+"M3,7L11,7",getExpandedButtonPath:()=>r.getRectPath(0,0,14,14,2)+"M3,7L11,7M7,3L7,11"}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.presetPrimaryColors=e.presetPalettes=e.generate=void 0;var r,i=n(30),o=(r=i)&&r.__esModule?r:{default:r};var a={red:"#F5222D",volcano:"#FA541C",orange:"#FA8C16",gold:"#FAAD14",yellow:"#FADB14",lime:"#A0D911",green:"#52C41A",cyan:"#13C2C2",blue:"#1890FF",geekblue:"#2F54EB",purple:"#722ED1",magenta:"#EB2F96",grey:"#666666"},s={};Object.keys(a).forEach((function(t){s[t]=(0,o.default)(a[t])})),e.generate=o.default,e.presetPalettes=s,e.presetPrimaryColors=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(t){for(var e=[],n=(0,o.default)(t),r=5;r>0;r-=1){var i=n.toHsv(),u=(0,o.default)({h:a(i,r,!0),s:s(i,r,!0),v:c(i,r,!0)}).toHexString();e.push(u)}e.push(n.toHexString());for(var h=1;h<=4;h+=1){var l=n.toHsv(),d=(0,o.default)({h:a(l,h),s:s(l,h),v:c(l,h)}).toHexString();e.push(d)}return e};var r,i=n(31),o=(r=i)&&r.__esModule?r:{default:r};function a(t,e,n){var r=void 0;return(r=Math.round(t.h)>=60&&Math.round(t.h)<=240?n?Math.round(t.h)-2*e:Math.round(t.h)+2*e:n?Math.round(t.h)+2*e:Math.round(t.h)-2*e)<0?r+=360:r>=360&&(r-=360),r}function s(t,e,n){if(0===t.h&&0===t.s)return t.s;var r=void 0;return(r=n?Math.round(100*t.s)-16*e:4===e?Math.round(100*t.s)+16:Math.round(100*t.s)+5*e)>100&&(r=100),n&&5===e&&r>10&&(r=10),r<6&&(r=6),r}function c(t,e,n){return n?Math.round(100*t.v)+5*e:Math.round(100*t.v)-15*e}},function(t,e,n){var r;!function(i){var o=/^\s+/,a=/\s+$/,s=0,c=i.round,u=i.min,h=i.max,l=i.random;function d(t,e){if(e=e||{},(t=t||"")instanceof d)return t;if(!(this instanceof d))return new d(t,e);var n=function(t){var e={r:0,g:0,b:0},n=1,r=null,s=null,c=null,l=!1,d=!1;"string"==typeof t&&(t=function(t){t=t.replace(o,"").replace(a,"").toLowerCase();var e,n=!1;if(I[t])t=I[t],n=!0;else if("transparent"==t)return{r:0,g:0,b:0,a:0,format:"name"};if(e=H.rgb.exec(t))return{r:e[1],g:e[2],b:e[3]};if(e=H.rgba.exec(t))return{r:e[1],g:e[2],b:e[3],a:e[4]};if(e=H.hsl.exec(t))return{h:e[1],s:e[2],l:e[3]};if(e=H.hsla.exec(t))return{h:e[1],s:e[2],l:e[3],a:e[4]};if(e=H.hsv.exec(t))return{h:e[1],s:e[2],v:e[3]};if(e=H.hsva.exec(t))return{h:e[1],s:e[2],v:e[3],a:e[4]};if(e=H.hex8.exec(t))return{r:L(e[1]),g:L(e[2]),b:L(e[3]),a:D(e[4]),format:n?"name":"hex8"};if(e=H.hex6.exec(t))return{r:L(e[1]),g:L(e[2]),b:L(e[3]),format:n?"name":"hex"};if(e=H.hex4.exec(t))return{r:L(e[1]+""+e[1]),g:L(e[2]+""+e[2]),b:L(e[3]+""+e[3]),a:D(e[4]+""+e[4]),format:n?"name":"hex8"};if(e=H.hex3.exec(t))return{r:L(e[1]+""+e[1]),g:L(e[2]+""+e[2]),b:L(e[3]+""+e[3]),format:n?"name":"hex"};return!1}(t));"object"==typeof t&&(q(t.r)&&q(t.g)&&q(t.b)?(f=t.r,g=t.g,p=t.b,e={r:255*T(f,255),g:255*T(g,255),b:255*T(p,255)},l=!0,d="%"===String(t.r).substr(-1)?"prgb":"rgb"):q(t.h)&&q(t.s)&&q(t.v)?(r=X(t.s),s=X(t.v),e=function(t,e,n){t=6*T(t,360),e=T(e,100),n=T(n,100);var r=i.floor(t),o=t-r,a=n*(1-e),s=n*(1-o*e),c=n*(1-(1-o)*e),u=r%6;return{r:255*[n,s,a,a,c,n][u],g:255*[c,n,n,s,a,a][u],b:255*[a,a,c,n,n,s][u]}}(t.h,r,s),l=!0,d="hsv"):q(t.h)&&q(t.s)&&q(t.l)&&(r=X(t.s),c=X(t.l),e=function(t,e,n){var r,i,o;function a(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t}if(t=T(t,360),e=T(e,100),n=T(n,100),0===e)r=i=o=n;else{var s=n<.5?n*(1+e):n+e-n*e,c=2*n-s;r=a(c,s,t+1/3),i=a(c,s,t),o=a(c,s,t-1/3)}return{r:255*r,g:255*i,b:255*o}}(t.h,r,c),l=!0,d="hsl"),t.hasOwnProperty("a")&&(n=t.a));var f,g,p;return n=B(n),{ok:l,format:t.format||d,r:u(255,h(e.r,0)),g:u(255,h(e.g,0)),b:u(255,h(e.b,0)),a:n}}(t);this._originalInput=t,this._r=n.r,this._g=n.g,this._b=n.b,this._a=n.a,this._roundA=c(100*this._a)/100,this._format=e.format||n.format,this._gradientType=e.gradientType,this._r<1&&(this._r=c(this._r)),this._g<1&&(this._g=c(this._g)),this._b<1&&(this._b=c(this._b)),this._ok=n.ok,this._tc_id=s++}function f(t,e,n){t=T(t,255),e=T(e,255),n=T(n,255);var r,i,o=h(t,e,n),a=u(t,e,n),s=(o+a)/2;if(o==a)r=i=0;else{var c=o-a;switch(i=s>.5?c/(2-o-a):c/(o+a),o){case t:r=(e-n)/c+(e>1)+720)%360;--e;)r.h=(r.h+i)%360,o.push(d(r));return o}function O(t,e){e=e||6;for(var n=d(t).toHsv(),r=n.h,i=n.s,o=n.v,a=[],s=1/e;e--;)a.push(d({h:r,s:i,v:o})),o=(o+s)%1;return a}d.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var t=this.toRgb();return(299*t.r+587*t.g+114*t.b)/1e3},getLuminance:function(){var t,e,n,r=this.toRgb();return t=r.r/255,e=r.g/255,n=r.b/255,.2126*(t<=.03928?t/12.92:i.pow((t+.055)/1.055,2.4))+.7152*(e<=.03928?e/12.92:i.pow((e+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:i.pow((n+.055)/1.055,2.4))},setAlpha:function(t){return this._a=B(t),this._roundA=c(100*this._a)/100,this},toHsv:function(){var t=g(this._r,this._g,this._b);return{h:360*t.h,s:t.s,v:t.v,a:this._a}},toHsvString:function(){var t=g(this._r,this._g,this._b),e=c(360*t.h),n=c(100*t.s),r=c(100*t.v);return 1==this._a?"hsv("+e+", "+n+"%, "+r+"%)":"hsva("+e+", "+n+"%, "+r+"%, "+this._roundA+")"},toHsl:function(){var t=f(this._r,this._g,this._b);return{h:360*t.h,s:t.s,l:t.l,a:this._a}},toHslString:function(){var t=f(this._r,this._g,this._b),e=c(360*t.h),n=c(100*t.s),r=c(100*t.l);return 1==this._a?"hsl("+e+", "+n+"%, "+r+"%)":"hsla("+e+", "+n+"%, "+r+"%, "+this._roundA+")"},toHex:function(t){return p(this._r,this._g,this._b,t)},toHexString:function(t){return"#"+this.toHex(t)},toHex8:function(t){return function(t,e,n,r,i){var o=[Y(c(t).toString(16)),Y(c(e).toString(16)),Y(c(n).toString(16)),Y(G(r))];if(i&&o[0].charAt(0)==o[0].charAt(1)&&o[1].charAt(0)==o[1].charAt(1)&&o[2].charAt(0)==o[2].charAt(1)&&o[3].charAt(0)==o[3].charAt(1))return o[0].charAt(0)+o[1].charAt(0)+o[2].charAt(0)+o[3].charAt(0);return o.join("")}(this._r,this._g,this._b,this._a,t)},toHex8String:function(t){return"#"+this.toHex8(t)},toRgb:function(){return{r:c(this._r),g:c(this._g),b:c(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+c(this._r)+", "+c(this._g)+", "+c(this._b)+")":"rgba("+c(this._r)+", "+c(this._g)+", "+c(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:c(100*T(this._r,255))+"%",g:c(100*T(this._g,255))+"%",b:c(100*T(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+c(100*T(this._r,255))+"%, "+c(100*T(this._g,255))+"%, "+c(100*T(this._b,255))+"%)":"rgba("+c(100*T(this._r,255))+"%, "+c(100*T(this._g,255))+"%, "+c(100*T(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":!(this._a<1)&&(k[p(this._r,this._g,this._b,!0)]||!1)},toFilter:function(t){var e="#"+m(this._r,this._g,this._b,this._a),n=e,r=this._gradientType?"GradientType = 1, ":"";if(t){var i=d(t);n="#"+m(i._r,i._g,i._b,i._a)}return"progid:DXImageTransform.Microsoft.gradient("+r+"startColorstr="+e+",endColorstr="+n+")"},toString:function(t){var e=!!t;t=t||this._format;var n=!1,r=this._a<1&&this._a>=0;return e||!r||"hex"!==t&&"hex6"!==t&&"hex3"!==t&&"hex4"!==t&&"hex8"!==t&&"name"!==t?("rgb"===t&&(n=this.toRgbString()),"prgb"===t&&(n=this.toPercentageRgbString()),"hex"!==t&&"hex6"!==t||(n=this.toHexString()),"hex3"===t&&(n=this.toHexString(!0)),"hex4"===t&&(n=this.toHex8String(!0)),"hex8"===t&&(n=this.toHex8String()),"name"===t&&(n=this.toName()),"hsl"===t&&(n=this.toHslString()),"hsv"===t&&(n=this.toHsvString()),n||this.toHexString()):"name"===t&&0===this._a?this.toName():this.toRgbString()},clone:function(){return d(this.toString())},_applyModification:function(t,e){var n=t.apply(null,[this].concat([].slice.call(e)));return this._r=n._r,this._g=n._g,this._b=n._b,this.setAlpha(n._a),this},lighten:function(){return this._applyModification(b,arguments)},brighten:function(){return this._applyModification(w,arguments)},darken:function(){return this._applyModification(M,arguments)},desaturate:function(){return this._applyModification(v,arguments)},saturate:function(){return this._applyModification(x,arguments)},greyscale:function(){return this._applyModification(y,arguments)},spin:function(){return this._applyModification(_,arguments)},_applyCombination:function(t,e){return t.apply(null,[this].concat([].slice.call(e)))},analogous:function(){return this._applyCombination(C,arguments)},complement:function(){return this._applyCombination(S,arguments)},monochromatic:function(){return this._applyCombination(O,arguments)},splitcomplement:function(){return this._applyCombination(P,arguments)},triad:function(){return this._applyCombination(A,arguments)},tetrad:function(){return this._applyCombination(E,arguments)}},d.fromRatio=function(t,e){if("object"==typeof t){var n={};for(var r in t)t.hasOwnProperty(r)&&(n[r]="a"===r?t[r]:X(t[r]));t=n}return d(t,e)},d.equals=function(t,e){return!(!t||!e)&&d(t).toRgbString()==d(e).toRgbString()},d.random=function(){return d.fromRatio({r:l(),g:l(),b:l()})},d.mix=function(t,e,n){n=0===n?0:n||50;var r=d(t).toRgb(),i=d(e).toRgb(),o=n/100;return d({r:(i.r-r.r)*o+r.r,g:(i.g-r.g)*o+r.g,b:(i.b-r.b)*o+r.b,a:(i.a-r.a)*o+r.a})},d.readability=function(t,e){var n=d(t),r=d(e);return(i.max(n.getLuminance(),r.getLuminance())+.05)/(i.min(n.getLuminance(),r.getLuminance())+.05)},d.isReadable=function(t,e,n){var r,i,o=d.readability(t,e);switch(i=!1,(r=function(t){var e,n;e=((t=t||{level:"AA",size:"small"}).level||"AA").toUpperCase(),n=(t.size||"small").toLowerCase(),"AA"!==e&&"AAA"!==e&&(e="AA");"small"!==n&&"large"!==n&&(n="small");return{level:e,size:n}}(n)).level+r.size){case"AAsmall":case"AAAlarge":i=o>=4.5;break;case"AAlarge":i=o>=3;break;case"AAAsmall":i=o>=7}return i},d.mostReadable=function(t,e,n){var r,i,o,a,s=null,c=0;i=(n=n||{}).includeFallbackColors,o=n.level,a=n.size;for(var u=0;uc&&(c=r,s=d(e[u]));return d.isReadable(t,s,{level:o,size:a})||!i?s:(n.includeFallbackColors=!1,d.mostReadable(t,["#fff","#000"],n))};var I=d.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},k=d.hexNames=function(t){var e={};for(var n in t)t.hasOwnProperty(n)&&(e[t[n]]=n);return e}(I);function B(t){return t=parseFloat(t),(isNaN(t)||t<0||t>1)&&(t=1),t}function T(t,e){(function(t){return"string"==typeof t&&-1!=t.indexOf(".")&&1===parseFloat(t)})(t)&&(t="100%");var n=function(t){return"string"==typeof t&&-1!=t.indexOf("%")}(t);return t=u(e,h(0,parseFloat(t))),n&&(t=parseInt(t*e,10)/100),i.abs(t-e)<1e-6?1:t%e/parseFloat(e)}function N(t){return u(1,h(0,t))}function L(t){return parseInt(t,16)}function Y(t){return 1==t.length?"0"+t:""+t}function X(t){return t<=1&&(t=100*t+"%"),t}function G(t){return i.round(255*parseFloat(t)).toString(16)}function D(t){return L(t)/255}var F,j,R,H=(j="[\\s|\\(]+("+(F="(?:[-\\+]?\\d*\\.\\d+%?)|(?:[-\\+]?\\d+%?)")+")[,|\\s]+("+F+")[,|\\s]+("+F+")\\s*\\)?",R="[\\s|\\(]+("+F+")[,|\\s]+("+F+")[,|\\s]+("+F+")[,|\\s]+("+F+")\\s*\\)?",{CSS_UNIT:new RegExp(F),rgb:new RegExp("rgb"+j),rgba:new RegExp("rgba"+R),hsl:new RegExp("hsl"+j),hsla:new RegExp("hsla"+R),hsv:new RegExp("hsv"+j),hsva:new RegExp("hsva"+R),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/});function q(t){return!!H.CSS_UNIT.exec(t)}t.exports?t.exports=d:void 0===(r=function(){return d}.call(e,n,e,t))||(t.exports=r)}(Math)},function(t,e,n){const r=n(18);function i(t){const e=t.getCurrentPage();this.matrixCache=e.getMatrix().slice(0),this._zoom(t),e.updateStatus()}function o(t){t.getCurrentPage().updateMatrix(this.matrixCache)}r.registerCommand("zoomTo",{_zoom(t){t.getCurrentPage().zoom(Number(this.zoom))},queue:!1,execute:i,back:o}),r.registerCommand("zoomIn",{enable(t){const e=t.getCurrentPage(),n=e.getMaxZoom(),r=e.getMinZoom(),i=e.getZoom();return i=i&&(o=i),e.zoom(o)},queue:!1,execute:i,back:o,shortcutCodes:[["metaKey","="],["ctrlKey","="]]}),r.registerCommand("zoomOut",{enable(t){const e=t.getCurrentPage(),n=e.getMaxZoom(),r=e.getMinZoom(),i=e.getZoom();return i>r||i===n},_zoom(t){const e=t.getCurrentPage(),n=e.getZoom(),r=e.getMinZoom();let i=n-t.get("_command").zoomDelta;i<=r&&(i=r),e.zoom(i)},queue:!1,execute:i,back:o,shortcutCodes:[["metaKey","-"],["ctrlKey","-"]]}),r.registerCommand("autoZoom",{enable:()=>!0,_zoom(t){t.getCurrentPage().autoZoom()},queue:!1,execute:i,back:o}),r.registerCommand("resetZoom",{enable:()=>!0,_zoom(t){t.getCurrentPage().resetZoom()},queue:!1,execute:i,back:o,shortcutCodes:[["metaKey","0"],["ctrlKey","0"]]})},function(t,e,n){const r=n(18);r.registerCommand("collapseExpand",{getItem(t){const e=t.getCurrentPage(),n=e.getGraph();return this.itemId?n.find(this.itemId):e.getSelected()[0]},enable(t){const e=this.getItem(t);return e&&!1!==e.collapseExpand&&e.getChildren().length>0},execute(t){const e=t.getCurrentPage(),n=e.getGraph(),r=this.getItem(t);r.getModel().collapsed?(n.update(r,{collapsed:!1}),r.getInnerEdges&&r.getInnerEdges().forEach(t=>{t.update()}),this.toCollapsed=!1):(n.update(r,{collapsed:!0}),this.toCollapsed=!0),e.clearSelected(),e.setSelected(r,!0),1===this.executeTimes&&(this.itemId=r.id)},back(t){const e=t.getCurrentPage(),n=e.getGraph(),r=this.getItem(t);this.toCollapsed?n.update(r,{collapsed:!1}):n.update(r,{collapsed:!0}),e.clearSelected(),e.setSelected(r,!0)},shortcutCodes:[["metaKey","/"],["ctrlKey","/"]]}),r.registerCommand("collapse",{enable(t){const e=this.getItem(t);return e&&!1!==e.collapseExpand&&e.getChildren().length>0&&!e.getModel().collapsed}},"collapseExpand"),r.registerCommand("expand",{enable(t){const e=this.getItem(t);return e&&!1!==e.collapseExpand&&e.getChildren().length>0&&e.getModel().collapsed}},"collapseExpand")},function(t,e,n){const r=n(4),i={INIT:"_initGraph"};i.AUGMENT={_initGraph(){const t=this.get("graph"),e=new(this.get("graphConstructor"))({page:this,...t});e.draw(),this.set("_graph",e)},changeMode(t){this.get("_graph").changeMode(t)},updateMatrix(t){this.get("_graph").updateMatrix(t)},getMode(){return this.get("_graph").get("mode")},getMatrix(){return this.get("_graph").getMatrix()},getZoom(){return this.get("_graph").getMatrix()[0]},getMaxZoom(){return this.get("_graph").get("maxZoom")},getMinZoom(){return this.get("_graph").get("minZoom")},getGraph(){return this.get("_graph")},getItems(){return this.get("_graph").getItems()},getNodes(){return this.get("_graph").getNodes()},translate(t,e){return this.get("_graph").translate(t,e)},getEdges(){return this.get("_graph").getEdges()},getGroups(){return this.get("_graph").getGroups()},render(){return this.get("_graph").render(),this},add(t,e){return this.get("_graph").add(t,e),this},focusPointByDom(t){return this.get("_graph").focusPointByDom(t),this},focusPoint(t){return this.get("_graph").focusPoint(t),this},find(t){return this.get("_graph").find(t)},focus(t){const e=this.get("_graph"),n=e.find(t);if(n){const t=n.getCenter();e.focusPoint(t)}return this},save(){return this.get("_graph").save()},read(t){this.get("_graph").read(t)},clear(){this.get("_graph").clear()},remove(t){return this.get("_graph").remove(t),this},update(t,e){return this.get("_graph").update(t,e),this},zoom(t,e){return this.get("_graph").zoom(t,e),this},getDomPoint(t){return this.get("_graph").getDomPoint(t)},getPoint(t){return this.get("_graph").getPoint(t)},zoomByDom(t,e){const n=this.get("_graph"),r=n.getPoint(t);return n.zoom(r,e),this},autoZoom(){return this.get("_graph").autoZoom(),this},resetZoom(){const t=this.get("_graph"),e=t.getWidth(),n=t.getHeight();return t.zoomByDom({x:e/2,y:n/2},1),this},css(t){const e=this.get("_graph").getMouseEventWrapper();r.modifyCSS(e,t)},setCapture(t){this.get("_graph").getRootGroup().set("capture",t)},destroy(){this.get("_graph").destroy()},delete(){const t=this.getSelected(),e=this.get("_graph");r.each(t,t=>{e.remove(t)})}},t.exports=i},function(t,e,n){const r=n(36),i=n(4),o={CFG:{grid:void 0},INIT:"_initGrid"};o.AUGMENT={_initGrid(){const t=this.get("grid"),e=this.get("_graph");if(t){const n=new r({page:this,graph:e,...t});this.setController("grid",n)}},showGrid(t){const e=this.get("_graph");let n=this.getController("grid");n||(t?i.isObject(t)&&this.set("grid",t):this.set("grid",!0),this._initGrid()),n=this.getController("grid"),n.show(),e.draw()},hideGrid(){const t=this.get("_graph"),e=this.getController("grid");e&&e.hide(),t.draw()},getGridCell(){return this.getController("grid").getCell()}},t.exports=o},function(t,e,n){const r=n(9),i=n(14),o=n(4);t.exports=class extends r{getDefaultCfg(){return{cell:16,line:i.gridStyle,type:"point",visible:!0}}init(){this._draw(),this._onViewPortChange(),!this.visible&&this.hide()}_onViewPortChange(){const t=this.graph;t.on("afterviewportchange",()=>{this.update()}),t.on("beforechangesize",()=>{this.update()})}_draw(){const t=this.graph,e=this._getPath(),n=t.getRootGroup(),r=o.mix({},this.line),i=t.getMatrix(),a=this.type,s="line"===a?1/i[0]:2/i[0];"point"===a&&(r.lineDash=null),r.lineWidth=s,r.path=e;const c=n.addShape("path",{attrs:r,capture:!1,zIndex:0});o.toBack(c,n),this.gridEl=c}show(){this.gridEl.show(),this.visible=!0}hide(){this.gridEl.hide(),this.visible=!1}_getLinePath(){const t=this.graph,e=t.get("width"),n=t.get("height"),r=t.getPoint({x:0,y:0}),i=t.getPoint({x:e,y:n}),o=this.cell,a=Math.ceil(r.x/o)*o,s=Math.ceil(r.y/o)*o,c=[];for(let t=0;t<=i.x-r.x;t+=o){const e=a+t;c.push(["M",e,r.y]),c.push(["L",e,i.y])}for(let t=0;t<=i.y-r.y;t+=o){const e=s+t;c.push(["M",r.x,e]),c.push(["L",i.x,e])}return c}_getPointPath(){const t=this.graph,e=t.get("width"),n=t.get("height"),r=t.getPoint({x:0,y:0}),i=2/t.getMatrix()[0],o=t.getPoint({x:e,y:n}),a=this.getCell(),s=Math.ceil(r.x/a)*a,c=Math.ceil(r.y/a)*a,u=[];for(let t=0;t<=o.x-r.x;t+=a){const e=s+t;for(let t=0;t<=o.y-r.y;t+=a){const n=c+t;u.push(["M",e,n]),u.push(["L",e+i,n])}}return u}getCell(){const t=this.cell,e=this.graph.getMatrix()[0];return t*e<9.6?9.6/e:t}_getPath(){const t=this.type;return this["_get"+o.upperFirst(t)+"Path"]()}update(t){o.mix(this,t);const e=this._getPath(),n=this.gridEl,r=this.graph.getMatrix(),i="line"===this.type?1/r[0]:2/r[0];n.attr("lineWidth",i),n.attr("path",e)}destroy(){const t=this.gridEl;t&&t.remove()}}},function(t,e){const n={};function r(t,e,n){t.on(n,t=>{e.emit(n,t)}),t.on("node:"+n,t=>{e.emit("node:"+n,t)}),t.on("edge:"+n,t=>{e.emit("edge:"+n,t)}),t.on("group:"+n,t=>{e.emit("group:"+n,t)}),t.on("anchor:"+n,t=>{e.emit("anchor:"+n,t)})}n.INIT="_initEvent",n.AUGMENT={_initEvent(){const t=this.get("_graph");r(t,this,"click"),r(t,this,"dblclick"),r(t,this,"mouseenter"),r(t,this,"mouseleave"),r(t,this,"mousedown"),r(t,this,"mouseup"),r(t,this,"contextmenu"),t.on("keydown",t=>{this.emit("keydown",t)}),t.on("keyup",t=>{this.emit("keyup",t)}),t.on("beforechange",t=>{this.emit("beforechange",t)}),t.on("afterchange",t=>{this.emit("afterchange",t)}),t.on("afterviewportchange",t=>{this.emit("afterviewportchange",t),t.updateMatrix[0]!==t.originMatrix[0]&&this.emit("afterzoom",t)}),t.on("beforeviewportchange",t=>{this.emit("beforeviewportchange",t),t.updateMatrix[0]!==t.originMatrix[0]&&this.emit("beforezoom",t)})}},t.exports=n},function(t,e,n){const r=n(4),i={CFG:{selectable:!0,multiSelectable:!0,_selectedCache:{}},INIT:"_initSelected"};i.AUGMENT={_initSelected(){const t=this.get("_graph");t.on("afteritemdraw",({item:t})=>{t.isSelected&&this.setItemSelected(t)}),t.on("beforeitemdestroy",t=>{this.clearItemSelected(t.item)})},setItemSelected(t){const e=this.get("_graph").getShapeObj(t).getSelectedStyle(t),n=t.getKeyShape();this.get("_selectedCache")[t.id]=t,e&&n.attr(e),t.isEdge&&(t.startArrow&&t.startArrow.attr({fill:e.stroke}),t.endArrow&&t.endArrow.attr({fill:e.stroke}))},clearItemSelected(t){const e=this.get("_graph"),n=t.getKeyShape(),i=e.getShapeObj(t),o=i.getStyle(t),a=i.getSelectedStyle(t),s=this.get("_selectedCache"),c=r.getContrast(o,a);if(n.attr(c),t.isEdge)try{t.startArrow&&t.startArrow.attr({fill:c.stroke}),t.endArrow&&t.endArrow.attr({fill:c.stroke})}catch(t){}delete s[t.id]},setSelected(t,e){const n=this.get("selectable"),i=this.get("_graph");if(!n)return;let o;o=r.isArray(t)?t:[t],r.each(o,t=>{r.isString(t)&&(t=i.find(t)),t&&!t.destroyed&&(e?(this.emit("beforeitemselected",{item:t}),this.setItemSelected(t),this.emit("afteritemselected",{item:t})):(this.emit("beforeitemunselected",{item:t}),this.clearItemSelected(t),this.emit("afteritemunselected",{item:t})),t.isSelected=e,this.updateStatus(),i.draw())})},getSelected(){const t=this.get("_selectedCache");return r.objectToValues(t)},clearSelected(){const t=this.get("_graph"),e=this.get("_selectedCache");r.each(e,t=>{t.isSelected&&this.setSelected(t,!1)}),t.draw()}},t.exports=i},function(t,e,n){const r=n(4),i={CFG:{activeable:!0,_activedCache:{}},INIT:"_initActived"};i.AUGMENT={_initActived(){const t=this.get("_graph");t.on("afteritemdraw",({item:t})=>{t.isActived&&this.setItemActived(t)}),t.on("beforeitemdestroy",t=>{this.clearItemActived(t.item)})},setItemActived(t){const e=this.get("_graph").getShapeObj(t),n=this.get("_activedCache"),r=e.getActivedStyle(t),i=t.getKeyShape();n[t.id]=t,r&&i.attr(r),t.isEdge&&(t.startArrow&&t.startArrow.attr({fill:r.stroke}),t.endArrow&&t.endArrow.attr({fill:r.stroke}))},clearItemActived(t){const e=this.get("_graph"),n=t.getKeyShape(),i=e.getShapeObj(t),o=i.getStyle(t),a=this.get("_activedCache"),s=i.getActivedStyle(t),c=r.getContrast(o,s);n.attr(c);try{t.isEdge&&(t.startArrow&&t.startArrow.attr({fill:c.stroke}),t.endArrow&&t.endArrow.attr({fill:c.stroke}))}catch(t){}delete a[t.id]},setActived(t,e){const n=this.get("activeable"),i=this.get("_graph");if(!n)return;let o;o=r.isArray(t)?t:[t],r.each(o,t=>{r.isString(t)&&(t=i.find(t)),t&&!t.destroyed&&(e?(this.emit("beforeitemactived",{item:t}),this.setItemActived(t),this.emit("afteritemactived",{item:t})):(this.emit("beforeitemunactived",{item:t}),this.clearItemActived(t),this.emit("afteritemunactived",{item:t})),t.isActived=e)}),i.draw()},getActived(){const t=this.get("_activedCache");return r.objectToValues(t)},clearActived(){const t=this.get("_graph"),e=this.get("_activedCache");r.each(e,t=>{t.isActived&&this.setActived(t,!1)}),t.draw()}},t.exports=i},function(t,e,n){const r=n(41),i={CFG:{align:{}},INIT:"_initAlign"};i.AUGMENT={_initAlign(){const t=this.get("align"),e=this.get("_graph"),n=new r({flow:this,graph:e,...t});this.setController("align",n)},align(t,e,n){return this.getController("align").align(t,e,n)},clearAlignLine(){return this.getController("align").clearAlignLine()}},t.exports=i},function(t,e,n){const r=n(9),i=n(14),o=n(4);function a(t,e){return{line:t,point:e,dis:o.pointLineDistance(t[0],t[1],t[2],t[3],e.x,e.y)}}t.exports=class extends r{getDefaultCfg(){return{line:i.alignLineStyle,item:!0,grid:!1,tolerance:5,_horizontalLines:{},_verticalLines:{},_alignLines:[]}}init(){this.item&&this._cacheBoxLine()}_cacheBoxLine(){const t=this.graph,e=this._horizontalLines,n=this._verticalLines,r=this.item;t.on("afteritemdraw",t=>{const i=t.item;if(!o.isEdge(i)){const t=i.getBBox();!0===r||"horizontal"===r?(e[i.id+"tltr"]=[t.minX,t.minY,t.maxX,t.minY,i],e[i.id+"lcrc"]=[t.minX,t.centerY,t.maxX,t.centerY,i],e[i.id+"blbr"]=[t.minX,t.maxY,t.maxX,t.maxY,i]):"center"===r&&(e[i.id+"lcrc"]=[t.minX,t.centerY,t.maxX,t.centerY,i]),!0===r||"vertical"===r?(n[i.id+"tlbl"]=[t.minX,t.minY,t.minX,t.maxY,i],n[i.id+"tcbc"]=[t.centerX,t.minY,t.centerX,t.maxY,i],n[i.id+"trbr"]=[t.maxX,t.minY,t.maxX,t.maxY,i]):"center"===r&&(n[i.id+"tcbc"]=[t.centerX,t.minY,t.centerX,t.maxY,i])}}),t.on("beforeitemdestroy",t=>{const r=t.item;delete e[r.id+"tltr"],delete e[r.id+"lcrc"],delete e[r.id+"blbr"],delete n[r.id+"tlbl"],delete n[r.id+"tcbc"],delete n[r.id+"trbr"]})}align(t,e){const n=o.mix({},t),r=this.flow.getController("grid");return this.grid&&r&&r.visible&&this._gridAlign(t,e),this.item&&this._itemAlign(t,e,n),t}_gridAlign(t,e){const n=this.flow,r=this.grid,i=n.getGridCell();if("cc"===r){const n=Math.round((t.x+e.width/2)/i)*i,r=Math.round((t.y+e.height/2)/i)*i;t.x=n-e.width/2,t.y=r-e.height/2}else t.x=Math.round(t.x/i)*i,t.y=Math.round(t.y/i)*i}_itemAlign(t,e,n){const r=this._horizontalLines,i=this._verticalLines,s=this.tolerance,c={x:n.x+e.width/2,y:n.y},u={x:n.x+e.width/2,y:n.y+e.height/2},h={x:n.x+e.width/2,y:n.y+e.height},l={x:n.x,y:n.y+e.height/2},d={x:n.x+e.width,y:n.y+e.height/2},f=[],g=[];let p=null;if(this.clearAlignLine(),o.each(r,t=>{t[4].isVisible()&&(f.push(a(t,c)),f.push(a(t,u)),f.push(a(t,h)))}),o.each(i,t=>{t[4].isVisible()&&(g.push(a(t,l)),g.push(a(t,u)),g.push(a(t,d)))}),f.sort((t,e)=>t.dis-e.dis),g.sort((t,e)=>t.dis-e.dis),0!==f.length&&f[0].dis{t.remove()}),this._alignLines=[]}_addAlignLine(t){const e=t.bbox,n=this.graph.getRootGroup(),r=this.line,i=this._alignLines;"item"===t.type&&(t.horizontals&&o.each(t.horizontals,t=>{const a=t.line,s=t.point,c=(a[0]+a[2])/2;let u,h;s.x{const a=t.line,s=t.point,c=(a[1]+a[3])/2;let u,h;s.y
    ',{position:"absolute",visibility:"hidden","z-index":"2",padding:"0px 2px 0px 0px",resize:"none",width:"auto",height:"auto",outline:"none",border:"1px solid #1890FF","transform-origin":"left top","max-width":"320px",background:"white","box-sizing":"content-box"});t.getGraphContainer().appendChild(e),e.on("blur",e=>{e.stopPropagation(),!t.destroyed&&this.endEditLabel()}),e.on("keydown",t=>{t.stopPropagation();const e=r.getKeyboradKey(t);(t.metaKey&&"s"===e||t.ctrlKey&&"s"===e)&&t.preventDefault(),"Enter"!==e&&"Escape"!==e||this.endEditLabel()}),this.set("labelTextArea",e),t.on("beforeviewportchange",()=>{e.focusItem&&this.setLabelEditorBeginPosition(e.focusItem)})}},_getLabelTextAreaBox(t,e,n=[0,0]){e&&t.attr("text",e);const i=this.getGraph().getRootGroup(),o=r.getBBox(t,i);return{minX:o.minX-n[1],minY:o.minY-n[0],maxX:o.maxX+n[1],maxY:o.maxY+n[0]}},setLabelEditorBeginPosition(t){const e=this.get("labelTextArea"),n=t.getLabel();if(n){const t=this._getLabelTextAreaBox(n),r=n.attr("lineHeight"),i=n.attr("fontSize"),o={x:t.minX,y:t.minY-r/4+i/4-1,width:t.maxX-t.minX,height:t.maxY-t.minY};e.css({top:o.y+"px",left:o.x+"px"}),e.labelPoint=o}else{const n=this.getGraph().getRootGroup(),i=t.getKeyShape(),o=r.getBBox(i,n),a={x:o.minY+(o.maxY-o.minY-e.height())/2,y:(o.minX+o.maxX)/2};e.css({top:a.x+"px",left:a.y+"px"}),e.labelPoint=a}},beginEditLabel(t){const e=this.get("labelTextArea"),n=this.getGraph();if(r.isString(t)&&(t=n.find(t)),t&&!t.destroyed&&e){this.setSignal("preventWheelPan",!0);const r=t.getModel(),i=t.getLabel(),o=n.getZoom();if(e.focusItem=t,i){const t=i.attr("lineHeight"),n=this._getLabelTextAreaBox(i),a=(n.maxX-n.minX)/o,s=(n.maxY-n.minY+t/4)/o;e.innerHTML=r.label,e.innerHTML=r.label,e.css({"min-width":a+"px","min-height":s+"px",visibility:"visible","font-family":i.attr("fontFamily"),"line-height":t+"px","font-size":i.attr("fontSize")+"px",transform:"scale("+o+")"})}else e.innerHTML="",e.css({"min-width":"auto","min-height":"auto"});this.setLabelEditorBeginPosition(t),e.css({visibility:"visible"}),e.focus(),document.execCommand("selectAll",!1,null)}},endEditLabel(){const t=this.get("labelTextArea");if(this.setSignal("preventWheelPan",!1),t){const e=t.focusItem;if(e){const n=e.getModel(),r=this.editor;n.label!==t.textContent&&r.executeCommand("update",{action:"updateLabel",itemId:e.id,updateModel:{label:t.textContent}}),t.hide(),t.focusItem=void 0,this.focusGraphWrapper()}}}},t.exports=i},function(t,e){const n={};n.AUGMENT={updateStatus(){const t=this.getSelected();let e;0===t.length?e="canvas-selected":1===t.length?t[0].isNode?e="node-selected":t[0].isEdge?e="edge-selected":t[0].isGroup&&(e="group-selected"):e="multi-selected",this.emit("statuschange",{status:e})}},t.exports=n},function(t,e,n){n(45),n(46),n(47),n(48),n(49),n(50),n(51),n(52),n(53),n(54),n(55),n(56),n(57),n(58),n(59),n(60),n(61),n(62)},function(t,e,n){const r=n(3),i=n(4);r.registerBehaviour("panBlank",i.getPanCanvasBehaviour(!0))},function(t,e,n){n(3).registerBehaviour("hoverButton",t=>{t.getGraph().behaviourOn("mouseenter",e=>{t.getSignal("panningItem")||e.shape&&e.shape.isButton&&t.css({cursor:"pointer"})})})},function(t,e,n){const r=n(3),i=n(4);r.registerBehaviour("panCanvas",i.getPanCanvasBehaviour())},function(t,e,n){const r=n(3),i=n(14),o=n(4);r.registerBehaviour("wheelChangeViewport",t=>{const e=t.getGraph();let n;e.behaviourOn("wheel",t=>{const{domEvent:e}=t;e.preventDefault()}),e.behaviourOn("wheel",o.throttle((function(r){if(t.getSignal("preventWheelPan"))return;const{domEvent:a}=r,s=t.getSignal("wheelZoom");n||t.setCapture(!1);if(s){const t=a.wheelDelta,n=1.05;if(Math.abs(t)>10){const i=e.getMatrix()[0];t>0?e.zoom({x:r.x,y:r.y},i*n):e.zoom({x:r.x,y:r.y},i*(1/n))}}else{const n=[],r=e.getMatrix();o.mat3.translate(n,r,[a.wheelDeltaX*i.wheelPanRatio,a.wheelDeltaY*i.wheelPanRatio]),t.translateLimt(n)&&e.updateMatrix(n)}n&&clearTimeout(n),n=setTimeout(()=>{t.setCapture(!0),n=void 0},50)}),16))})},function(t,e,n){n(3).registerBehaviour("processPanItem",t=>{const e=t.getGraph();e.behaviourOn("mousemove",n=>{const r=t.get("panItemDelegation");if(r){const i=t.get("panItemStartPoint"),o=t.get("panItemStartBox"),a=n.x-i.x,s=n.y-i.y,c=t.align({x:o.minX+a,y:o.minY+s},{width:o.width,height:o.height});r.attr({x:c.x,y:c.y}),e.emit("itempanning",n),e.draw()}})})},function(t,e,n){n(3).registerBehaviour("startPanItem",t=>{const e=t.getGraph(),n=e.getRootGroup();e.behaviourOn("dragstart",r=>{if(2===r.button||!r.item||!r.item.isNode&&!r.item.isGroup)return;const i=r.item;let o;if(o=i.isSelected?t.getSelected():[i],o=o.filter(t=>t.isNode||t.isGroup),o[0]&&!1!==o[0].dragable){e.emit("beforepanitem",{items:o}),e.emit("beforeshowdelegation",{items:o});const i=t.getDelegation(o,n),a=i.getBBox();t.setSignal("panningItem",!0),t.set("panItems",o),t.set("panItemDelegation",i),t.set("panItemStartBox",a),t.set("panItemStartPoint",{x:r.x,y:r.y}),e.draw()}})})},function(t,e,n){n(3).registerBehaviour("endPanItem",t=>{const e=t.getGraph();e.behaviourOn("panitemend",()=>{const n=t.get("panItemDelegation");n&&(n.remove(),e.draw()),t.setSignal("panningItem",!1),t.set("panItemDelegation",void 0),t.set("panItemStartPoint",void 0),t.set("panItemStartBox",void 0),t.set("panItems",void 0)}),e.behaviourOn("canvas:mouseleave",()=>{t.get("panItems")&&(t.clearAlignLine(),e.emit("panitemend"))})})},function(t,e,n){n(3).registerBehaviour("dblclickItemEditLabel",t=>{t.getGraph().behaviourOn("node:dblclick",e=>{e.shape&&!e.shape.isButton&&t.beginEditLabel(e.item)})})},function(t,e,n){n(3).registerBehaviour("clickCanvasSelected",t=>{const e=t.getGraph();e.behaviourOn("click",e=>{e.shape||(t.clearSelected(),t.clearActived(),t.updateStatus())}),e.behaviourOn("contextmenu",e=>{e.shape||(t.clearSelected(),t.clearActived(),t.updateStatus())})})},function(t,e,n){n(3).registerBehaviour("clickCollapsedButton",t=>{const e=t.getGraph();e.behaviourOn("click",n=>{const r=n.item,i=n.shape;if(r&&i&&i.isCollapsedButton){const n=t.editor;n?n.executeCommand("collapseExpand",{itemId:r.id}):e.update(r,{collapsed:!0})}})})},function(t,e,n){n(3).registerBehaviour("clickEdgeSelected",t=>{t.getGraph().behaviourOn("edge:click",e=>{t.get("multiSelectable")&&!0===t.getSignal("shiftKeyDown")||(t.clearActived(),t.clearSelected()),t.setSelected(e.item.id,!0)})})},function(t,e,n){n(3).registerBehaviour("clickExpandedButton",t=>{const e=t.getGraph();e.behaviourOn("click",n=>{const r=n.item,i=n.shape;if(r&&i&&i.isExpandedButton){const n=t.editor;n?n.executeCommand("collapseExpand",{itemId:r.id}):e.update(r,{collapsed:!1})}})})},function(t,e,n){n(3).registerBehaviour("clickGroupSelected",t=>{t.getGraph().behaviourOn("group:click",e=>{t.get("multiSelectable")&&!0===t.getSignal("shiftKeyDown")||(t.clearActived(),t.clearSelected()),t.setSelected(e.item.id,!0)})})},function(t,e,n){n(3).registerBehaviour("clickNodeSelected",t=>{t.getGraph().behaviourOn("node:click",e=>{t.get("multiSelectable")&&!0===t.getSignal("shiftKeyDown")||(t.clearActived(),t.clearSelected()),t.setSelected(e.item.id,!0)})})},function(t,e,n){n(3).registerBehaviour("hoverNodeActived",t=>{const e=t.getGraph();let n;e.behaviourOn("node:mouseenter",e=>{const{item:r}=e;!1!==r.getShapeObj().panAble&&t.css({cursor:"move"}),t.getSignal("panningItem")||t.getSignal("dragEdge")||e.item&&e.item.isSelected||(n=e.item,t.setActived(n,!0))}),e.behaviourOn("node:mouseleave",e=>{const r=e.toShape;n&&(r&&r.isAnchor&&r.getItem()===n||t.getSignal("dragEdge")||(n.isSelected||t.setActived(n,!1),n=void 0))})})},function(t,e,n){n(3).registerBehaviour("hoverGroupActived",t=>{const e=t.getGraph();e.behaviourOn("mouseenter",e=>{t.getSignal("panningItem")||e.item&&e.item.isSelected||t.getSignal("dragEdge")||e.shape&&e.shape.isGroupKeyShape&&(t.css({cursor:"move"}),t.setActived(e.item,!0))}),e.behaviourOn("group:mouseleave",e=>{e.item.isActived&&!e.item.isSelected&&t.setActived(e.item,!1)})})},function(t,e,n){n(3).registerBehaviour("hoverEdgeActived",t=>{const e=t.getGraph();e.behaviourOn("edge:mouseenter",e=>{t.getSignal("panningItem")||e.item&&e.item.isSelected||t.getSignal("dragEdge")||t.setActived(e.item,!0)}),e.behaviourOn("edge:mouseleave",e=>{t.setActived(e.item,!1)})})},function(t,e,n){n(3).registerBehaviour("keydownCmdWheelZoom",t=>{const e=t.getGraph();e.behaviourOn("keydown",e=>{91===e.domEvent.keyCode&&t.setSignal("wheelZoom",!0)}),e.behaviourOn("keyup",e=>{91===e.domEvent.keyCode&&t.setSignal("wheelZoom",!1)})})},function(t,e,n){const r=n(22);t.exports={dragingEdgeEndPoint({endPointType:t,edgeModel:e,graph:n,delegation:r,startPoint:i,ev:o,source:a,target:s}){const{item:c}=o,u="source"===t?[o,i]:[i,o];c&&("source"===t?s=c:a=c);const h=n.getShapeObj("edge",e).getPathByPoints({points:u,source:a,target:s});r.attr("path",h),n.draw()},panGroup(t,e,n,i){const o=t.getModel();r.traverseTree(t,r=>{if("node"===r.type){const t=r.getModel();i.update(r,{x:t.x+e,y:t.y+n})}t.getCrossEdges&&t.getCrossEdges().forEach(t=>{t.update()})},t=>"group"===t.type?t.getChildren():[]),i.update(t,{x:o.x+e,y:o.y+n})},dropUpdateEdge({ev:t,endPointType:e,model:n,diagram:r}){const i=r.get("noEndEdge"),o=r.get("linkAnchor"),a=r.get("linkNode"),{item:s,shape:c,x:u,y:h}=t;if(r.getGraph().emit("beforedropedge"),c){if(o&&c.isAnchor&&c.hasHotspot){const t=c,r=t.getItem();return"target"===e?(n.target=r.id,n.targetAnchor=t.getIndex(),!0):(n.source=r.id,n.sourceAnchor=t.getIndex(),!0)}if(a&&s&&s.isNode)return"target"===e?(n.target=s.id,!0):(n.source=s.id,!0)}else if(i)return"target"===e?(n.target={x:u,y:h},!0):(n.source={x:u,y:h},!0);return!1}}},function(t,e){t.exports={rectRectCrossAlgorithm(t,e){const n=Math.max(t.minX,e.minX),r=Math.max(t.minY,e.minY),i=Math.min(t.maxX,e.maxX),o=Math.min(t.maxY,e.maxY);return n>i||r>o},euclideanDistance:{pointPoint(t,e){const n=Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2);return Math.sqrt(n)}}}},function(t,e,n){const r={},i=n(2);r.AUGMENT={changeAddEdgeModel(t){this.set("addEdgeModel",t)},cancelAdd(){this.set("addType",void 0),this.set("addModel",void 0),this.changeMode("default")},beginAdd(t,e){this.set("addType",t),this.set("addModel",e),this.changeMode("add")},endAdd(){this.set("addType",void 0),this.set("addModel",void 0),this.changeMode("default")},delete(){const t=this.getSelected(),e=this.get("_graph");i.each(t,t=>{e.remove(t)})},toBack(){const t=this.getSelected(),e=this.get("_graph");t.sort((t,e)=>{const n=t.getGraphicGroup(),r=e.getGraphicGroup();return i.getIndex(r)-i.getIndex(n)}),t.forEach(t=>{e.toBack(t)})},toFront(){const t=this.getSelected(),e=this.get("_graph");t.sort((t,e)=>{const n=t.getGraphicGroup(),r=e.getGraphicGroup();return i.getIndex(n)-i.getIndex(r)}),t.forEach(t=>{e.toFront(t)})},addGroup(t){const e=this.get("_graph"),n=this.getSelected();let r,o=!0;if(0===n.length)return;t||(t={label:"新建分组"}),i.setId(t),e.add("group",t),e.toFront(t.id);const a=e.find(t.id);if(n.forEach(t=>{const e=t.getParent();e&&(r?r!==e&&(o=!1):r=e)}),!o)return void console.warn("add group elements must have the same parent");r&&(t.parent=r.getModel().id),n.forEach(n=>{e.update(n,{parent:t.id})});const s=a.getInnerEdges();a.deepEach(t=>{e.toFront(t)}),s.forEach(t=>{e.toFront(t)})},unGroup(){const t=this.get("_graph"),e=this.getSelected(),n=e[0];1===e.length&&i.isGroup(n)&&(n.getChildren().forEach(e=>{t.update(e,{parent:void 0}),e.collapsedParent||e.show(),e.isGroup&&e.deepEach(t=>{t.collapsedParent||t.show()})}),t.remove(n))},newGroup(t){this.addGroup(t)}},t.exports=r},function(t,e,n){const r=n(67),i=n(2),o={CFG:{anchor:{}},INIT:"_initAnchor"};o.AUGMENT={_initAnchor(){const t=this.get("anchor"),e=this.get("_graph");if(t){const n=new r({diagram:this,graph:e,...t});this.setController("anchor",n)}},showAnchor(t,e,n){this.getController("anchor").showAnchor(t,e,n)},clearAnchor(t){this.getController("anchor").clearAnchor(t)},setHotspotActived(t,e){this.getController("anchor").setHotspotActived(t,e)},hoverShowAnchor(t){const e=t.getAnchorPoints(),n=[];e.forEach((e,r)=>{const i={anchor:e,item:t};this.emit("hovernode:beforeshowanchor",i),i.cancel||n.push(r)}),this.showAnchor(t,n)},anchorHasBeenLinked(t,e){const n=t.getEdges(),r=[];return n.forEach(e=>{const n=e.getModel();n.source!==t.id||i.isNil(n.sourceAnchor)||r.push(n.sourceAnchor),n.target!==t.id||i.isNil(n.targetAnchor)||r.push(n.targetAnchor)}),i.isObject(e)?-1!==r.indexOf(e.index):-1!==r.indexOf(e)},dragEdgeBeforeShowAnchor(t,e,n){this.getGraph().getNodes().forEach(r=>{const i=[],o=r.getAnchorPoints();let a;if(t.isNode){const s=t.getAnchorPoints();o.forEach((o,c)=>{a="target"===n?{source:t,sourceAnchor:s[e],target:r,targetAnchor:o,dragEndPointType:n}:{target:t,targetAnchor:s[e],source:r,sourceAnchor:o,dragEndPointType:n},this.emit("dragedge:beforeshowanchor",a),a.cancel||i.push(c)})}else o.forEach((t,e)=>{i.push(e)});r===t&&r.isAnchorShow?i.forEach(t=>{const n=r.getAnchor(t);e!==t&&n&&n.showHotspot()}):this.showAnchor(r,i,!0)})}},t.exports=o},function(t,e,n){const r=n(9),i=n(5),o=n(2);t.exports=class extends r{getDefaultCfg(){return{_anchorItemCache:{}}}init(){const t=this.graph;t.on("afteritemdraw",t=>{t.item.isAnchorShow&&this.showAnchor(t.item)}),t.on("beforeitemdestroy",t=>{this._clearAnchor(t.item)}),t.on("afteritemhide",t=>{t.item.isNode&&this._clearAnchor(t.item)})}_updateAnchor(t){const e=this.graph;t.anchorShapes.forEach(t=>{t.updatePosition()}),e.draw()}_drawAnchor(t,e,n,r){const a=t.getAnchorPoints();this._clearAnchor(t),o.each(a,(o,a)=>{if(n&&-1===n.indexOf(a))return;let s;const c=e.addShape("marker",{attrs:{symbol:"circle",...i.anchorPointStyle,x:o.x,y:o.y},freezePoint:o,item:t,index:a,eventPreFix:"anchor",isItemChange(){},zIndex:i.zIndex.anchorPoint});c.toFront(),c.eventPreFix="anchor",c.showHotspot=()=>{s=e.addShape("marker",{attrs:{symbol:"circle",...i.anchorHotsoptStyle,x:o.x,y:o.y},freezePoint:o,capture:!1,zIndex:i.zIndex.anchorHotsopt}),t.anchorShapes.push(s),c.hasHotspot=!0,s.toFront(),c.toFront()},c.getIndex=()=>a,c.getItem=()=>t,c.getPoint=()=>o,c.updatePosition=()=>{const e=t.getAnchorPoints()[a];c.attr(e)},c.setActived=()=>{c.attr(i.anchorPointHoverStyle)},c.clearActived=()=>{c.attr(i.anchorPointStyle)},c.isAnchor=!0,c.setHotspotActived=t=>{s&&(t?s.attr(i.anchorHotsoptActivedStyle):s.attr(i.anchorHotsoptStyle))},r&&c.showHotspot(),t.anchorShapes.push(c),t.getAllAnchors=()=>t.anchorShapes.filter(t=>t.isAnchor),t.getAnchor=e=>t.anchorShapes.find(t=>t.get("index")===e)})}_clearAnchor(t){t.anchorShapes&&t.anchorShapes.forEach(t=>{t.remove()}),t.anchorShapes=[]}setHotspotActived(t,e){const n=this.diagram.getGraph();t.setHotspotActived(e),n.draw()}showAnchor(t,e,n){if(!t.isVisible())return;const r=this.graph,i=this._anchorItemCache,o=r.getRootGroup();this._drawAnchor(t,o,e,n),t.isAnchorShow=!0,i[t.id]=t}clearAnchor(t){const e=this.graph,n=e.get("itemCache"),r=this._anchorItemCache;let i=t;i=o.isObject(t)?[t]:o.isString(t)?[n[t]]:r,o.each(i,t=>{this._clearAnchor(t),t.isAnchorShow=!1,delete r[t.id]}),e.draw()}}},function(t,e,n){const r=n(69),i={CFG:{orbit:null},INIT:"_initOrbit"};i.AUGMENT={_initOrbit(){const t=this.get("orbit");if(t){const e=new r({diagram:this,...t});this.setController("orbit",e)}},showOrbit(t){this.getController("orbit").show(t)},hideOrbit(){this.getController("orbit").hide()},layoutOrbit(t,e){this.getController("orbit").layout(t,e)}},t.exports=i},function(t,e,n){const r=n(9),i=n(5),o=n(70),{vec2:a,isString:s}=n(2);t.exports=class extends r{getDefaultCfg(){return{satellite:[],satelliteCache:[]}}init(){const t=this.satellite,e=this.diagram,n={diagram:e};e.getGraph().addBehaviour("orbit"),t.forEach(t=>{s(t)?this.satelliteCache.push(new o[t](n)):this.satelliteCache.push(new o({...n,...t}))})}layout(t,e){const n=this.diagram,r=n.getGraph(),{satelliteCache:o}=this,s=t.getBBox(),{centerX:c,centerY:u}=s,h=n.getZoom(),l=i.orbitGap/h,d=o.filter(t=>t.isVisible()),f=[e.x-c,e.y-u],g=a.length(f),p=s.width/2+l,m=a.scale([],f,p/g);d.forEach(t=>{const e=t.getDOM(),n=e.width()/2,i=r.getDomPoint({x:m[0]+c,y:m[1]+u});e.css({top:i.y-n+"px",left:i.x-n+"px"})})}show(t){const{satelliteCache:e}=this;e.forEach(e=>{e.enable()&&(e.item=t,e.show())})}hide(){const{satelliteCache:t}=this;t.forEach(t=>{t.hide()})}}},function(t,e,n){const r=n(23);r.forkAndLink=n(71),t.exports=r},function(t,e,n){const r=n(23),i=n(2);t.exports=class extends r{getDefaultCfg(){return{name:"forkAndLink",render:()=>'\n
    \n ',bindEvent(t,e){const n=e.getGraph(),r=n.getRootGroup();t.setAttribute("draggable","true");return[i.addEventListener(t,"dragstart",()=>{const n=this.item,i={...e.get("addEdgeModel"),source:n.id},o=n.getBBox(),a=e.getDelegation([{isEdge:!0}],r);e.setSignal("dragEdge",!0),e.beginAdd("edge",i),e.set("addEdgeConfig",{addModel:i,delegation:a,startPoint:{x:o.centerX,y:o.centerY},sourceItem:n}),t.hide()}),i.addEventListener(t,"click",({clientX:t,clientY:r})=>{const o=e.editor,a=this.item,s=n.getPointByClient({x:t,y:r}),c=a.getBBox(),u=a.getModel(),h=[s.x-c.centerX,s.y-c.centerY],l=i.vec2.length(h);if(i.vec2.scale(h,h,160/l),o)o.executeCommand("copyAdjacent",{copyNode:a,x:s.x+h[0],y:s.y+h[1]});else{const t=i.clone(u);t.x=s.x+h[0],t.y=s.y+h[1],n.add(a.type,t)}})]}}}}},function(t,e,n){const r=n(2),i=n(5),o={};function a(t){t.controlPointShapes&&r.each(t.controlPointShapes,t=>{t.remove()}),t.controlPointShapes=[],t.isControlPointShow=!1}o.INIT="_initResize",o.CFG={nodeResizeable:!1,edgeResizeable:!0},o.AUGMENT={_initResize(){const t=this.get("_graph"),e=this.get("nodeResizeable"),n=this.get("edgeResizeable");e&&t.on("afteritemdraw",t=>{"node"===t.item.type&&t.item.isVisible()&&this.drawControlPoints(t.item)}),n&&t.on("afteritemdraw",t=>{"edge"===t.item.type&&t.item.isVisible()&&this.drawControlPoints(t.item)}),t.on("afteritemhide",t=>{var e;t.item.isControlPointShow&&((e=t.item).controlPointShapes&&r.each(e.controlPointShapes,t=>{t.hide()}),e.isControlPointShow=!1)}),t.on("afteritemshow",t=>{var e;!t.item.isControlPointShow&&((e=t.item).controlPointShapes&&r.each(e.controlPointShapes,t=>{t.show()}),e.isControlPointShow=!0)}),t.on("beforeitemdestroy",t=>{t.item.isControlPointShow&&a(t.item)})},drawControlPoints(t){const e=this.get("_graph").getRootGroup(),n=this.get("nodeResizeable"),o=this.get("edgeResizeable");"node"===t.type?n&&function(t,e){const n=t.getBBox(),o=[{x:n.minX,y:n.minY},{x:n.maxX,y:n.minY},{x:n.minX,y:n.maxY},{x:n.maxX,y:n.maxY}];a(t);const s=e.addShape("rect",{attrs:r.mix({},i.nodeSelectedBoxStyle,{symbol:"square",x:n.minX,y:n.minY,width:n.maxX-n.minX,height:n.maxY-n.minY})});t.controlPointShapes.push(s),r.each(o,n=>{const o=e.addShape("marker",{attrs:r.mix({},i.nodeControlPointStyle,{symbol:"square",x:n.x,y:n.y}),freezePoint:{x:n.x,y:n.y},item:t});t.controlPointShapes.push(o)})}(t,e):"edge"===t.type&&o&&function(t,e){const n=t.getKeyShape().attr("path"),o=n[0],s=o.length,c=n[n.length-1],u=c.length,h=[{x:o[s-2],y:o[s-1]},{x:c[u-2],y:c[u-1]}],l=t.getModel();a(t),r.each(h,(n,o)=>{const a=e.addShape("marker",{attrs:r.mix({},i.edgeControlPointStyle,{x:n.x,y:n.y}),freezePoint:{x:n.x,y:n.y},item:t});a.eventPreFix="edgeControlPoint",a.getSourcePoint=()=>h[0],a.getTargetPoint=()=>h[h.length-1],a.getItem=()=>t,a.isSourceEndPoint=()=>l.source&&0===o,a.isTargetEndPoint=()=>l.target&&o===h.length-1,t.controlPointShapes.push(a)})}(t,e),t.isControlPointShow=!0}},t.exports=o},function(t,e,n){const r=n(2),i={};i.AUGMENT={addOutterShape(t,e){this.clearOutterShape(t);const{lineWidth:n}=e,i=t.getKeyShape(),o=t.getGraphicGroup(),a=i.attr(),s=i.get("type"),c=i.attr("lineWidth"),u=r.clone(a);delete u.fillStyle,delete u.strokeStyle,delete u.matrix;const h=o.addShape(s,{attrs:{...u,fill:null,...e}});r.toBack(h,o);const l=h.getBBox(),d=l.maxX-l.minX,f=l.maxY-l.minY,g=(l.minX+l.maxX)/2,p=(l.minY+l.maxY)/2;h.transform([["t",-g,-p],["s",(n+d+c)/d,(n+f+c)/f],["t",g,p]]),h.isOutter=!0,t.outterShape=h},clearOutterShape(t){t.outterShape&&t.outterShape.remove()}},t.exports=i},function(t,e){const n={CFG:{linkNode:!0,linkAnchor:!0},INIT:"_initLink"};n.AUGMENT={_initLink(){const t=this.getGraph(),e=this.get("linkAnchor"),n=this.get("linkNode"),r=t.get("mode");e&&(this.on("beforeitemactived",t=>{const e=t.item;e.isNode&&this.hoverShowAnchor(e)}),this.on("beforeitemunactived",t=>{const e=t.item;(e.isNode||e.isGroup)&&this.clearAnchor(e)}),this.on("beforeitemselected",t=>{const e=t.item;(e.isNode||e.isGroup)&&this.hoverShowAnchor(e)}),this.on("beforeitemunselected",t=>{const e=t.item;(e.isNode||e.isGroup)&&this.clearAnchor(e)}),t.addBehaviour("dragAnchorAddEdge","add"),t.addBehaviour("hoverAnchorActived","default"),t.changeMode(r)),n&&(t.addBehaviour("hoverNodeAddOutter","add"),t.addBehaviour("hoverNodeAddOutter","default"),t.changeMode(r))}},t.exports=n},function(t,e,n){n(76),n(77),n(78),n(79)},function(t,e,n){const r=n(13),i=n(2),o=n(5);r.registerNode("diagram-base",{getSize(t){const e=t.getModel();return i.getNodeSize(e.size)},defaultFillPalette:0,defaultStrokePalette:3,activedFillPalette:0,activedStrokePalette:5,selectedFillPalette:2,selectedStrokePalette:5,getDefaulStyle:()=>o.nodeStyle,getDefaulActivedStyle:()=>o.nodeActivedStyle,getDefaulSelectedtyle:()=>o.nodeSelectedStyle,getStyle(t){const e=t.getModel(),n=e.color;let r,o;if(n){const t=i.Palettes.generate(n);r=t[this.defaultFillPalette],o=t[this.defaultStrokePalette]}return i.mix(!0,{},this.getDefaulStyle(),{fill:r,stroke:o},e.style)},getPath(t){const e=this.getSize(t),n=this.getStyle(t);return i.getRectPath(-e[0]/2,-e[1]/2,e[0],e[1],n.radius)},getActivedOutterStyle:()=>o.nodeActivedOutterStyle,getActivedStyle(t){const e=t.getModel(),n=this.getDefaulActivedStyle(t),{color:r}=e;if(r){const t=i.Palettes.generate(r);return{...n,fill:t[this.activedFillPalette],stroke:t[this.activedStrokePalette]}}return n},getSelectedStyle(t){const e=t.getModel(),n=this.getDefaulSelectedtyle(t),{color:r}=e;if(r){const t=i.Palettes.generate(r);return{...n,fill:t[this.selectedFillPalette],stroke:t[this.selectedStrokePalette]}}return n},getSelectedOutterStyle(t){const e=t.getModel(),{color:n}=e;if(n){const t=i.Palettes.generate(n);return{...o.nodeSelectedOutterStyle,stroke:t[1],fill:t[1]}}return o.nodeSelectedOutterStyle},anchor:[[.5,0],[1,.5],[.5,1],[0,.5]]}),r.registerNode("capsule",{getPath(t){const e=this.getSize(t);return i.getRectPath(-e[0]/2,-e[1]/2,e[0],e[1],e[1]/2)}}),r.registerNode("circle",{getPath(t){const e=this.getSize(t),n=e[0],r=e[1];return i.getEllipsePath(0,0,n/2,r/2)}}),r.registerNode("rhombus",{getPath(t){const e=this.getSize(t),n=e[0],r=e[1],o=[{x:0,y:0-r/2},{x:0+n/2,y:0},{x:0,y:0+r/2},{x:0-n/2,y:0},{x:0,y:0-r/2}];return i.pointsToPolygon(o)}})},function(t,e,n){const r=n(13),i=n(2),o=n(5);r.registerEdge("diagram-base",{getPath(t){const e=t.getPoints(),n=t.getSource(),r=t.getTarget();return this.getPathByPoints({points:e,source:n,target:r,item:t})},getPathByPoints:({points:t})=>i.pointsToPolygon(t),getStyle(t){const e=t.getModel();return i.mix(!0,{},o.edgeStyle,{stroke:e.color},e.style)},getActivedStyle:()=>o.edgeActivedStyle,getSelectedStyle:()=>o.edgeSelectedStyle,getActivedOutterStyle(){},getSelectedOutterStyle(){}})},function(t,e,n){const r=n(13),i=n(5),o=n(2),a=o.getGroupIconPath(),s=o.getCollapsedButtonPath(),c=o.getExpandedButtonPath(),u={fill:"#CED4D9"},h={stroke:"#697B8C",fill:"#fff",fillOpacity:0},l={stroke:"#697B8C",fill:"#fff",fillOpacity:0},d={fill:"#000000",textBaseline:"top",textAlign:"left"},f={stroke:"#CED4D9",fill:"#F2F4F5",radius:4},g=i.groupBackgroundPadding,p=184-g[1]-g[3],m=40-g[0]-g[2];r.registerGroup("diagram-base",{draw(t){const e=t.getModel(),n=t.getGraphicGroup(),r=t.getChildrenBBox(),i=this.getStyle(t),f=e.collapsed,v=e.padding?e.padding:g;if(r.minX===1/0&&(r.minX=e.x,r.maxX=e.x+p,r.minY=e.y,r.maxY=e.y+m),f&&(r.minX=r.maxX-p,r.maxY=r.minY+m),r.maxX-r.minXi.groupActivedStyle,getSelectedStyle:()=>i.groupSelectedStyle,getSelectedOutterStyle:()=>i.groupSelectedOutterStyle,getActivedOutterStyle(){},intersectBox:"rect"})},function(t,e,n){n(13).registerGuide("diagram-base")},function(t,e,n){n(81),n(82),n(83),n(84),n(85),n(86),n(87),n(88),n(89),n(90),n(91),n(92),n(93),n(94)},function(t,e,n){const r=n(1),i=n(2);r.registerBehaviour("panItem",t=>{const e=t.getGraph();e.behaviourOn("drop",()=>{const n=t.get("panItems");if(!n)return;const r=n[0],o=n.map(t=>t.id),a=t.get("panItemDelegation"),s=t.get("panItemStartBox"),c=r.id,u=a.attr("x")-s.minX,h=a.attr("y")-s.minY;function l(){o.forEach(t=>{const n=e.find(t),r=n.getModel();n.isGroup?i.panGroup(n,u,h,e):(e.update(n,{x:r.x+u,y:r.y+h}),e.toFront(n))}),1===o.length&&(t.clearSelected(),t.setSelected(c,!0))}e.emit("afterpanitemdrop",{panItems:n}),t.clearAlignLine();const d=t.editor;e.emit("panitemend"),!d||t.getSignal("dragaddnodetogroup")?l():d.executeCommand(l)})},["startPanItem","processPanItem","endPanItem"])},function(t,e,n){const r=n(1),i=n(5);r.registerBehaviour("hoverAnchorActived",t=>{const e=t.getGraph();e.behaviourOn("anchor:mouseenter",n=>{if(t.getSignal("panningItem")||t.getSignal("dragEdge"))return;const r=n.shape,o=r.getItem(),a=o.getModel(),s={...t.get("addEdgeModel"),source:a.id},c={anchor:r.getPoint(),item:o};t.emit("hoveranchor:beforeaddedge",c),c.cancel?t.css({cursor:i.cursor.hoverUnEffectiveAnchor}):(t.css({cursor:i.cursor.hoverEffectiveAnchor}),!r.get("destroyed")&&r.setActived(),t.beginAdd("edge",s),e.draw())}),e.behaviourOn("anchor:mouseleave",n=>{if(!t.getSignal("dragEdge")&&!t.getSignal("panningItem")){const r=n.shape,o=r.getItem();t.css({cursor:i.cursor.beforePanCanvas}),o.isSelected||(t.clearAnchor(o),t.setActived(o,!1)),!r.get("destroyed")&&r.clearActived(),t.cancelAdd(),e.draw()}})})},function(t,e,n){const r=n(1),i=n(5);r.registerBehaviour("hoverEdgeControlPoint",t=>{t.getGraph().behaviourOn("edgeControlPoint:mouseenter",e=>{if(t.getSignal("dragEdge")||t.getSignal("panningItem"))return;const n=e.shape;(n.isTargetEndPoint()||n.isSourceEndPoint())&&t.css({cursor:i.cursor.hoverEdgeControllPoint})})})},function(t,e,n){const r=n(1),i=n(5),o=n(2);r.registerBehaviour("dragEdgeControlPoint",t=>{const e=t.getGraph(),n=e.getRootGroup();let r,a,s,c,u,h,l,d,f,g;function p(n){if(!s)return;const p={};e.getNodes().forEach(e=>{t.clearAnchor(e)}),t.css({cursor:i.cursor.beforePanCanvas}),s.remove();const m=o.dropUpdateEdge({ev:n,endPointType:r?"target":"source",model:p,diagram:t});e.show(f);const v=f.id;if(m){const n=t.editor;n?n.executeCommand("update",{itemId:v,updateModel:p}):e.update(v,p)}t.setSignal("dragEdge",!1),r=void 0,a=void 0,s=void 0,c=void 0,u=void 0,h=void 0,l=void 0,d=void 0,f=void 0,g=void 0}e.behaviourOn("edgeControlPoint:mousedown",i=>{if(2===i.button)return;const o=i.shape;o.isTargetEndPoint()?(f=o.getItem(),g=f.getModel(),r=o,c=o.getSourcePoint(),u=f.getSource(),l=g.sourceAnchor):o.isSourceEndPoint()&&(f=o.getItem(),g=f.getModel(),a=o,c=o.getTargetPoint(),h=f.getTarget(),d=g.targetAnchor),f&&(s=t.getDelegation([f],n),u?t.dragEdgeBeforeShowAnchor(u,l,"target"):h&&t.dragEdgeBeforeShowAnchor(h,d,"source"),e.hide(f),t.setSignal("dragEdge",!0))}),e.behaviourOn("mousemove",t=>{s&&o.dragingEdgeEndPoint({endPointType:u?"target":"source",edgeModel:g,graph:e,delegation:s,startPoint:c,ev:t,originSource:u,originTarget:h})}),e.behaviourOn("edgeControlPoint:mouseleave",e=>{r||a||e.toShape||t.css({cursor:i.cursor.beforePanCanvas})}),e.behaviourOn("mouseup",p),e.behaviourOn("canvas:mouseleave",p)},["dragHoverAnchorHotspot"])},function(t,e,n){const r=n(1),i=n(2);r.registerBehaviour("dragPanelItemAddNode",t=>{const e=t.getGraph(),n=e.getRootGroup();let r,o,a,s,c;function u(){t.setSignal("panningItem",!1),t.set("panItemDelegation",void 0),t.set("panItemStartBox",void 0),t.set("panItemStartPoint",void 0),o=void 0,a=void 0,s=void 0,c=void 0}e.behaviourOn("canvas:mouseenter",e=>{if(!o&&(s=t.get("addType"),c=t.get("addModel"),c=i.clone(c),"node"===s)){a=i.getNodeSize(c.size);const s=a[0]/2,u=a[1]/2;r={minX:e.x-s,minY:e.y-u,maxX:e.x+s,maxY:e.y+u,width:a[0],height:a[1]},o=t.getDelegation([r],n),t.setSignal("panningItem",!0),t.set("panItemDelegation",o),t.set("panItemStartBox",r),t.set("panItemStartPoint",{x:e.x,y:e.y})}}),e.behaviourOn("mouseup",n=>{if(!o)return;c.x=n.x,c.y=n.y;const r=s;i.setId(c);const a=t.editor;o.remove(),t.endAdd(),t.clearAlignLine(),t.clearSelected(),t.focusGraphWrapper(),a?a.executeCommand("add",{type:"node",addModel:c}):e.add(r,c),t.setSelected(e.find(c.id),!0),u()}),e.behaviourOn("canvas:mouseleave",()=>{o&&(t.clearAlignLine(),o.remove(),e.draw(),t.cancelAdd(),u())})},["processPanItem"])},function(t,e,n){n(1).registerBehaviour("dragHoverAnchorHotspot",t=>{const e=t.getGraph();e.behaviourOn("anchor:dragenter",e=>{if(t.getSignal("dragEdge")){const n=e.shape;t.setHotspotActived(n,!0)}}),e.behaviourOn("anchor:dragleave",e=>{if(t.getSignal("dragEdge")){const n=e.shape;t.setHotspotActived(n,!1)}})})},function(t,e,n){const r=n(1),i=n(2);r.registerBehaviour("dragAnchorAddEdge",t=>{const e=t.getGraph(),n=e.getRootGroup();e.behaviourOn("anchor:dragstart",e=>{if(2===e.button)return;const r=e.shape,o=r.get("freezePoint"),a=r.getItem(),s=i.clone(t.get("addModel")),c=r.getIndex();s.source=a.id,s.sourceAnchor=c;const u={x:o.x,y:o.y},h=t.getDelegation([{isEdge:!0}],n);t.setSignal("dragEdge",!0),t.dragEdgeBeforeShowAnchor(a,c,"target"),t.set("addEdgeConfig",{addModel:s,delegation:h,startPoint:u,sourceItem:a})})},["processAddEdge","dragHoverAnchorHotspot","hoverAnchorActived"])},function(t,e,n){const r=n(1),i=n(5);r.registerBehaviour("dragMultiSelect",t=>{const e=t.getGraph(),n=e.getRootGroup();let r,o;function a(){t.css({cursor:i.cursor.beforePanCanvas}),r=void 0,o=void 0}t.css({cursor:i.cursor.multiSelect}),e.behaviourOn("dragstart",t=>{2!==t.button&&(r={x:t.x,y:t.y},o=n.addShape("rect",{attrs:i.multiSelectRectStyle}))}),e.behaviourOn("drag",t=>{o&&(o.attr({x:Math.min(r.x,t.x),y:Math.min(r.y,t.y),width:Math.abs(t.x-r.x),height:Math.abs(t.y-r.y)}),e.draw())}),e.behaviourOn("dragend",()=>{if(!o)return;const n=e.getNodes().map(t=>t.id),r=o.getBBox();function i(){t.clearSelected(),n.forEach(n=>{const i=e.find(n),o=i.getBBox();o.minX>r.minX&&o.minY>r.minY&&o.maxX{o&&(o.remove(),e.draw(),a())})})},function(t,e,n){n(1).registerBehaviour("keydownShiftMultiSelected",t=>{const e=t.getGraph();e.behaviourOn("keydown",e=>{e.domEvent.shiftKey&&t.setSignal("shiftKeyDown",!0)}),e.behaviourOn("keyup",e=>{e.domEvent.shiftKey||t.setSignal("shiftKeyDown",!1)})})},function(t,e,n){const r=n(1),i=n(5);r.registerBehaviour("dragNodeAddToGroup",t=>{const e=t.getGraph();let n,r;function o(){t.setSignal("dragaddnodetogroup",!1),n=void 0,r=void 0}e.behaviourOn("dragenter",o=>{if(!t.getSignal("panningItem"))return;const a=t.get("panItems");a[0]&&a[0].isNode&&1===a.length&&o.item&&o.item.isGroup&&a[0].getParent()!==o.item&&(n=a[0],r=o.item,e.update(r,{padding:i.groupBackgroundPadding.map(t=>t+4),style:i.dragNodeHoverToGroupStyle}))}),e.behaviourOn("dragleave",()=>{r&&n&&e.update(r,{padding:void 0,style:void 0})}),e.behaviourOn("drop",i=>{if(!r||!n||r!==i.item)return;t.setSignal("dragaddnodetogroup",!0);const o=n.id,a=r.id;function s(){e.update(o,{parent:a})}e.update(a,{padding:void 0,style:void 0,collapsed:!1});const c=t.editor;c?c.executeCommand(s):s()}),e.behaviourOn("dragend",()=>{o()}),e.behaviourOn("canvas:mouseleave",()=>{r&&(e.update(r,{padding:void 0,style:void 0}),o())})})},function(t,e,n){const r=n(1),i=n(5),o=n(2);r.registerBehaviour("dragOutFromGroup",t=>{const e=t.getGraph();let n,r,a,s=!1;function c(){clearTimeout(a),r&&e.update(r,{padding:void 0,style:void 0}),s=!1,n=void 0,r=void 0}e.behaviourOn("drag",c=>{t.getSignal("panningItem")&&!s&&(clearTimeout(a),a=setTimeout(()=>{const a=t.get("panItems");if(a){if(n=a[0],r=a[0].getParent(),n&&1===a.length&&r&&!c.shape){const n=t.get("panItemDelegation").getBBox(),a=r.getBBox();o.rectRectCrossAlgorithm(n,a)&&(e.update(r,{padding:i.groupBackgroundPadding.map(t=>t-8),style:i.dragNodeLeaveFromGroupStyle}),s=!0)}s||(n=void 0,r=void 0)}},i.outFromGroupDelayTime))}),e.behaviourOn("dragenter",t=>{n&&r&&(r===t.item&&e.update(r,{padding:i.groupBackgroundPadding.map(t=>t+4),style:i.dragNodeHoverToGroupStyle}),s=!1)}),e.on("drop",t=>{r&&n&&!t.shape&&(e.update(n,{parent:void 0}),e.update(r,{style:void 0}),c())}),e.on("dragend",()=>{c()}),e.behaviourOn("canvas:mouseleave",()=>{c()})})},function(t,e,n){const r=n(1),i=n(2),o=n(5);r.registerBehaviour("processAddEdge",t=>{const e=t.getGraph();function n(){t.setSignal("dragEdge",!1),t.set("addEdgeConfig",{addModel:void 0,delegation:void 0,startPoint:void 0,sourceItem:void 0})}e.behaviourOn("mousemove",n=>{const r=t.get("addEdgeConfig");if(!r)return;const{addModel:o,delegation:a,startPoint:s,sourceItem:c}=r;a&&i.dragingEdgeEndPoint({endPointType:"target",edgeModel:o,graph:e,delegation:a,startPoint:s,ev:n,sourceItem:c})}),e.behaviourOn("mouseup",r=>{const a=t.get("addEdgeConfig");if(!a)return;const{addModel:s,delegation:c,sourceItem:u}=a,h=t.editor;if(!c)return void n();e.getNodes().forEach(e=>{t.clearAnchor(e)}),t.clearAnchor(u),t.setActived(u,!1),t.setSelected(u,!1),t.css({cursor:o.cursor.beforePanCanvas}),c.remove();i.dropUpdateEdge({ev:r,endPointType:"target",model:s,diagram:t})&&(h?h.executeCommand("add",{type:"edge",addModel:s}):e.add("edge",s)),e.draw(),t.endAdd(),n()}),e.behaviourOn("canvas:mouseleave",()=>{const r=t.get("addEdgeConfig");if(!r)return;const{delegation:i,sourceItem:o}=r;if(!i)return void n();e.getNodes().forEach(e=>{t.clearAnchor(e)}),t.setActived(o,!1),t.clearAnchor(o),i.remove(),t.cancelAdd(),e.draw(),n()})})},function(t,e,n){n(1).registerBehaviour("hoverNodeAddOutter",t=>{const e=t.getGraph();let n;e.behaviourOn("node:mouseenter",({item:e})=>{t.getSignal("dragEdge")&&(n=e,t.addOutterShape(e,{stroke:"#52C41A",strokeOpacity:.45,lineWidth:4}))}),e.behaviourOn("node:mouseleave",()=>{n&&t.clearOutterShape(n)}),e.behaviourOn("beforedropedge",()=>{n&&t.clearOutterShape(n)})})},function(t,e,n){const r=n(1),i=n(5),o=n(2);r.registerBehaviour("orbit",t=>{const e=t.getGraph();let n;e.behaviourOn("beforepanitem",()=>{t.hideOrbit()}),e.behaviourOn("node:mouseenter",({item:e})=>{t.getSignal("panningItem")||t.getSignal("dragEdge")||(n=e,t.showOrbit(e))}),e.on("beforeviewportchange",()=>{t.hideOrbit()}),e.behaviourOn("mousemove",({item:r,x:a,y:s})=>{if(!n)return;const c=n.getBBox(),u=o.euclideanDistance.pointPoint({x:c.centerX,y:c.centerY},{x:a,y:s}),h=e.getMatrix()[0];r!==n&&u>c.width/2+i.orbitGap/h&&(t.hideOrbit(),n=void 0),n&&t.layoutOrbit(n,{x:a,y:s})})})},function(t,e,n){const r=n(11);n(96),t.exports=r},function(t,e,n){n(97),n(98),n(99),n(100),n(101)},function(t,e,n){const r=n(11);r.registerNode("flow-base",{}),r.registerNode("flow-html",{},["html"]),r.registerNode("flow-rect",{},"flow-base"),r.registerNode("flow-capsule",{},"capsule"),r.registerNode("flow-circle",{},"circle"),r.registerNode("flow-rhombus",{},"rhombus")},function(t,e,n){n(11).registerEdge("flow-base",{})},function(t,e,n){const r=n(19),i=n(11);function o(t,e,n,r){const i=r?r/2:30,o=r;if(t<=e&&e<=n||t>=e&&e>=n){const r=(n-e)/2,a=Math.abs(r);if(0===r)return t===e?0:(e-t)/Math.abs(e-t)*i;if(a>o){const t=r/a*o;return Math.abs(t)o&&(a=o),at?a:-a}function a(t,e,n,r){const i=t.bbox,a=function(t,e){const n=Math.abs(t.x-e.centerX),r=Math.abs(t.y-e.centerY);return n/e.width>r/e.height}(e,i);let s,c;s=c=0;let u=Math.min(i.height,i.width);return r&&r.bbox&&(u=Math.min(u,r.bbox.height,r.bbox.width)),a?s=o(i.centerX,e.x,n.x,u):c=o(i.centerY,e.y,n.y,u),{x:e.x+s,y:e.y+c}}function s(t,e){const n=t.x,r=t.y;return{x:n+.1*(e.x-n),y:r+.1*(e.y-r)}}function c(t,e,n){const i=t[0],o=t[t.length-1],c=["M",i.x,i.y],u=function(t,e,n,r){return[n&&n.bbox?a(n,t,e,r):s(t,e),r&&r.bbox?a(r,e,t,n):s(e,t)]}(i,o,e,n),h=["C"],l=[c];return r.each(u,(function(t){h.push(t.x,t.y)})),h.push(o.x,o.y),l.push(h),l}i.registerEdge("flow-smooth",{getPathByPoints:({points:t,source:e,target:n})=>c(t,e,n)},"flow-edge")},function(t,e,n){const r=n(19),i=n(11),o={offset:16,borderRadius:5};function a(t,e){const n=Math.min(t.minX,e.minX),r=Math.min(t.minY,e.minY),i=Math.max(t.maxX,e.maxX),o=Math.max(t.maxY,e.maxY);return{centerX:(n+i)/2,centerY:(r+o)/2,minX:n,minY:r,maxX:i,maxY:o,height:o-r,width:i-n}}function s(t,e){return 2*Math.abs(t.centerX-e.centerX)r/e.height}(e,t)?{x:e.x>t.centerX?t.maxX:t.minX,y:e.y}:{x:e.x,y:e.y>t.centerY?t.maxY:t.minY}}function l(t){const{minX:e,minY:n,maxX:r,maxY:i}=t;return[{x:e,y:n},{x:r,y:n},{x:r,y:i},{x:e,y:i}]}function d(t,e){const{x:n,y:r}=t;return ne.maxX||re.maxY}function f(t,e,n,r){const i=e.x-t.x,o=e.y-t.y,a=r.x-n.x,s=r.y-n.y,c=(-o*(t.x-n.x)+i*(t.y-n.y))/(-a*o+i*s),u=(a*(t.y-n.y)-s*(t.x-n.x))/(-a*o+i*s);return c>=0&&c<=1&&u>=0&&u<=1}function g(t,e,n){if(n.width===n.height===0)return!1;const[r,i,o,a]=l(n);return f(t,e,r,i)||f(t,e,r,a)||f(t,e,i,o)||f(t,e,o,a)}function p(t){return t=v(t)}function m(t,e){return[t,{x:t.x,y:e.y},e]}function v(t){const e=[],n={};return t.forEach(t=>{const e=t.id=`${t.x}-${t.y}`;n[e]=t}),r.each(n,t=>{e.push(t)}),e}function x(t,e){return Math.abs(t.x-e.x)+Math.abs(t.y-e.y)}function y(t,e,n,r,i){return x(t,e)+x(t,n)+function(t,e){let n=0;return e.forEach(e=>{e&&(t.x===e.x&&(n+=-2),t.y===e.y&&(n+=-2))}),n}(t,[e,n,r,i])}function b(t,e){const n=t.indexOf(e);n>-1&&t.splice(n,1)}function w(t,e,n,r){const i=[];return t.forEach(t=>{t!==e&&(t.x!==e.x&&t.y!==e.y||g(t,e,n)||g(t,e,r)||i.push(t))}),v(i)}function M(t,e,n,r,i=0){t.unshift(e[r]),n[r]&&n[r]!==r&&i<=100&&M(t,e,n,n[r],i+1)}function _(t,e,n,r,i){const o=n&&n.bbox?n.bbox:c(t),f=r&&r.bbox?r.bbox:c(e);if(s(o,f))return p(m(t,e));const g=u(o,i),_=u(f,i);if(s(g,_))return p(m(t,e));const S=h(g,t),A=h(_,e),E=function(t=[]){const e=[],n=[];t.forEach(t=>{e.push(t.x),n.push(t.y)});const r=Math.min.apply(Math,e),i=Math.max.apply(Math,e),o=Math.min.apply(Math,n),a=Math.max.apply(Math,n);return{centerX:(r+i)/2,centerY:(o+a)/2,maxX:i,maxY:a,minX:r,minY:o,height:a-o,width:i-r}}([S,A]),P=(a(g,_),a(g,E)),C=a(_,E);let O=[];O=O.concat(l(P)),O=O.concat(l(C));const I={x:(t.x+e.x)/2,y:(t.y+e.y)/2};[E,P,C].forEach(t=>{O=O.concat(function(t,e){return function(t,e){return et.maxX?[]:[{x:e,y:t.minY},{x:e,y:t.maxY}]}(t,e.x).concat(function(t,e){return et.maxY?[]:[{x:t.minX,y:e},{x:t.maxX,y:e}]}(t,e.y))}(t,I).filter(t=>d(t,g)&&d(t,_)))}),[{x:S.x,y:A.y},{x:A.x,y:S.y}].forEach(t=>{d(t,g)&&d(t,_)&&O.push(t)}),O.unshift(S),O.push(A),O=v(O);const k=function(t,e,n,r,i,o,a){const s=[],c=[e],u={},h={},l={};h[e.id]=0,l[e.id]=y(e,n,e);const d={};for(t.forEach(t=>{d[t.id]=t});c.length;){let f,g=1/0;if(c.forEach(t=>{l[t.id]{if(-1!==s.indexOf(t))return;-1===c.indexOf(t)&&c.push(t);const r=l[f.id]+x(f,t);h[t.id]&&r>=h[t.id]||(u[t.id]=f.id,h[t.id]=r,l[t.id]=h[t.id]+y(t,n,e,o,a))})}return console.error("cannot find path: ",t,e,n),[e,n]}(O,S,A,o,f,t,e);return k.unshift(t),k.push(e),p(k)}function S(t,e){const n=[],r=t[0];return n.push(`M${r.x} ${r.y}`),t.forEach((r,i)=>{const o=t[i+1],a=t[i+2];if(o&&a)if(function(t,e,n){return!(t.x===e.x===n.x||t.y===e.y===n.y)}(r,o,a)){const[t,i]=function(t,e,n,r){const i=x(t,e),o=x(n,e);return ii.toAllPadding([0,2]),getPathByPoints({points:t,source:e,target:n,item:r}){if(e&&n){const t=i.getParallelEdges(n,e);let a=i.getParallelEdges(e,n),s=a.indexOf(r);return e===n?(a=a.filter(t=>{const r=t.getModel();return r.source===e.id&&r.target===n.id}),s=a.indexOf(r),function(t,e){const n=t.getBBox(),r=[n.centerX,n.centerY],i=n.width/2,a=50*(e+1)+50,s=[r[0]-i/2,r[1]-Math.sqrt(3)/2*i],c=[s[0]-r[0],s[1]-r[1]],u=o.scale([],c,(i+a)/i),h=[r[0]+u[0],r[1]+u[1]],l=[r[0]+i/2,r[1]-Math.sqrt(3)/2*i],d=[l[0]-r[0],l[1]-r[1]],f=o.scale([],d,(i+a)/i),g=[r[0]+f[0],r[1]+f[1]];return[["M",s[0],s[1]],["C",h[0],h[1],g[0],g[1],l[0],l[1]]]}(e,s)):(0===t.length&&s--,function(t,e,n){const r=t.getBBox(),i=e.getBBox(),a=r.centerX,s=r.centerY,c=20*(n+1),u=[.5*(i.centerX+a)-a,.5*(i.centerY+s)-s],h=[-u[1],u[0]],l=o.length(h);o.scale(h,h,c/l);const d={x:u[0]+h[0]+a,y:u[1]+h[1]+s},f=t.getLinkPoints(d)[0],g=e.getLinkPoints(d)[0];return[["M",f.x,f.y],["Q",d.x,d.y,g.x,g.y]]}(e,n,s))}return i.pointsToPolygon(t)}})},function(t,e,n){n(17).registerGroup("koni-base",{})},function(t,e,n){const r=n(8);r.Util=n(10),n(108),n(112),n(120),t.exports=r},function(t,e,n){n(109),n(110),n(111)},function(t,e,n){const r=n(1),i=n(10);r.registerBehaviour("panMindNode",t=>{const e=t.getGraph();let n,r,o;function a(){n.nth=r;const i=e.add("node",n);t.setSelected(i,!0),o&&e.remove(o.id)}function s(){e.emit("panitemend"),n=void 0,o=void 0,r=void 0}e.behaviourOn("beforeshowdelegation",()=>{t.clearSelected(),t.clearActived()}),e.behaviourOn("node:dragstart",t=>{if(2===t.button)return;const i=t.item;n=i.getModel(),!n.parent||t.shape.isCollapsedButton||t.shape.isExpandedButton?s():(r=e.getNth(i),e.remove(i))}),e.behaviourOn("itempanning",n=>{if(n.shape&&n.shape.isPlaceholder)return;const r=t.getHotArea(n),i=t.getRoot();if(o&&(r?o.id!==r.id&&e.remove(e.find(o.id)):e.remove(e.find(o.id))),o=r,r){const t=r.parent;if(!e.find(r.id)){const n={id:r.id,parent:t.id,isPlaceholder:!0,parentModel:t,baseline:o.parent.id===i.id?"center":void 0,shape:"mind-placeholder",nth:r.nth};r.side&&(n.side=r.side),e.add("node",n)}}}),e.behaviourOn("drop",()=>{if(n)if(o){const a=i.clone(n);e.remove(o.id),t.executeCommand("moveMindNode",{model:a,newParentId:o.parent.id,newNth:o.nth,newSide:o.side,originParentId:n.parent,originNth:r,originSide:n.side})}else a();s()}),e.behaviourOn("canvas:mouseleave",()=>{n&&(a(),s())})},["startPanItem","processPanItem","endPanItem"])},function(t,e,n){n(1).registerBehaviour("keydownMoveSelection",t=>{t.getGraph().on("keydown",e=>{t._moveItemSelection(e)})})},function(t,e,n){n(1).registerBehaviour("keydownEditLabel",t=>{t.getGraph().behaviourOn("keydown",e=>{t.showLabelEditor(e)})})},function(t,e,n){n(113),n(114),n(115),n(116),n(117),n(118),n(119)},function(t,e,n){const r=n(8),i=n(10),o={fill:"#000",textAlign:"left",textBaseline:"top"},a={stroke:"#959EA6",strokeOpacity:0,fill:"#959EA6",cursor:"pointer"},s={stroke:"#434B54",fill:"#fff",cursor:"pointer"};r.registerNode("mind-base",{dy:4,afterDraw(t){const e=t.getModel();e.children&&e.children.length>0&&e.collapsed&&this.drawExpandedButton(t)},debugDrawLayoutPoint(t){const e=t.getModel();t.getGraphicGroup().addShape("circle",{attrs:{x:e.x,y:e.y,r:5,fill:"red"}})},drawExpandedButton(t){const e=t.getKeyShape().getBBox(),n=t.getGraphicGroup().addGroup(),r=n.addShape("path",{attrs:{path:i.getRectPath(0,0,16,7,3),...a}}),o=r.getBBox(),s=i.getMindNodeSide(t),c=this.getButtonPositon(e,o,s),u={fill:"white",r:1};n.addShape("circle",{attrs:{...u,x:4,y:3.5},capture:!1}),n.addShape("circle",{attrs:{...u,x:8,y:3.5},capture:!1}),n.addShape("circle",{attrs:{...u,x:12,y:3.5},capture:!1}),r.attr("lineAppendWidth",20),n.translate(c.x,c.y),r.isExpandedButton=!0,r.isButton=!0},drawCollapsedButton(t){const e=t.getKeyShape().getBBox(),n=t.getGraphicGroup().addShape("path",{attrs:{path:i.getCollapsedButtonPath(),...s}}),r=n.getBBox(),o=i.getMindNodeSide(t),a=this.getButtonPositon(e,r,o);n.translate(a.x,a.y),n.isCollapsedButton=!0,n.isButton=!0},getButtonPositon:(t,e,n)=>"right"===n?{x:t.maxX+2,y:t.maxY-(e.maxY-e.minY)/2}:{x:t.minX-(e.maxX-e.minX)-2,y:t.maxY-(e.maxY-e.minY)/2},getLabel:t=>t.getModel().label,getPadding:()=>[4,8,4,8],getSize(t){const e=t.getModel(),n=t.getGraphicGroup(),r=e.size;if(e.size){if(i.isArray(r))return r;if(i.isNumber(r))return[r,r]}const o=n.findByClass("label")[0],a=this.getPadding(t),s=o.getBBox();return[s.width+a[1]+a[3],s.height+a[0]+a[2]]},getPath(t){const e=this.getSize(t),n=this.getStyle(t);return i.getRectPath(-e[0]/2,-e[1]/2+this.dy,e[0],e[1],n.radius)},drawLabel(t){const e=t.getGraphicGroup();let n=this.getLabel(t);const r=this.getLabelStyle(t);n||(n=" ");const a=i.mix(!0,{},o,r,{x:0,y:0});i.isObject(n)?i.mix(a,n):a.text=n;const s=e.addShape("text",{class:"label",attrs:a});return this.adjustLabelText(s),this.adjustLabelPosition(t,s),s},adjustLabelText(t){let e=t.attr("text");const n=t.getBBox();if(n.maxX-n.minX>400){const n=t.attr("font");e=i.getLabelTextByTextLineWidth(e,n),t.attr("text",e)}},adjustLabelPosition(t,e){const n=this.getSize(t),r=this.getPadding(),i=n[0],o=e.getBBox();e.attr({x:-i/2+r[3],y:-o.height/2+this.dy})},getLabelStyle:()=>({fill:"rgba(38,38,38,0.85)",lineHeight:18,fontSize:12}),getStyle:()=>({fill:"#ccc",fillOpacity:0,radius:4,lineWidth:2}),getActivedStyle:()=>({stroke:"#44C0FF",lineWidth:2}),getSelectedStyle:()=>({stroke:"#1AA7EE",lineWidth:2}),anchor:[[0,1],[1,1]]})},function(t,e,n){n(8).registerNode("mind-first-sub",{dy:0,getPadding:()=>[6,12,8,12],getLabelStyle:()=>({fill:"rgba(38,38,38,0.85)",fontWeight:500,fontSize:14,lineHeight:20})})},function(t,e,n){n(8).registerNode("mind-second-sub",{dy:0,getPadding:()=>[8,4,8,4],getLabelStyle:()=>({fill:"rgba(38,38,38,0.85)",fontSize:12,lineHeight:20})})},function(t,e,n){const r=n(8),i=n(10);r.registerNode("mind-root",{adjustLabelPosition(t,e){const n=e.getBBox();e.attr({x:-n.width/2,y:-n.height/2-1})},getPath(t){const e=this.getSize(t),n=this.getStyle(t);return i.getRectPath(-e[0]/2,-e[1]/2,e[0],e[1],n.radius)},getButtonPositon:(t,e,n)=>"right"===n?{x:t.maxX+2,y:(t.maxY+t.minY)/2-(e.maxY-e.minY)/2}:{x:t.minX-(e.maxX-e.minX)-2,y:(t.maxY+t.minY)/2-(e.maxY-e.minY)/2},getPadding:()=>i.toAllPadding([12,24]),getStyle:()=>({fill:"#587EF7",stroke:"#587EF7",fillOpacity:1,radius:4}),getLabelStyle:()=>({fontSize:20,fill:"white",lineHeight:28}),drawExpandedButton(){},drawCollapsedButton(){},panAble:!1,anchor:[[.45,.5],[.55,.5]]},"mind-first-sub")},function(t,e,n){const r=n(8),i=n(10);r.registerNode("mind-placeholder",{afterDraw(t){t.getKeyShape().isPlaceholder=!0},getPath(t){const e=t.getModel(),{parentModel:n}=e,r=this.getStyle(t);let o,a=0;return n.hierarchy<=2?o=28:(o=20,a=4),i.getRectPath(-27.5,-o/2+a,55,o,r.radius)},getStyle:()=>({fill:"#91D5FF",radius:4,lineWidth:3}),drawExpandedButton(){},drawCollapsedButton(){},anchor:()=>[[0,1],[1,1]]})},function(t,e,n){n(8).registerEdge("mind-edge",{getEdetal:t=>t.children&&t.children.length>0&&!t.collapsed?2===t.hierarchy?24:18:0,getPath(t){const e=t.getPoints(),n=t.getSource(),r=t.getTarget(),i=n.getBBox(),o=r.getBBox(),a=r.getModel();let s=14,c=4;if(2===a.hierarchy&&(s=66,c=30),e[0].y===e[1].y){const t=3===a.hierarchy?24:18,n=this.getEdetal(a);return i.centerX=3){const t=3===a.hierarchy?24:18,n=this.getEdetal(a);if(i.centerXt.getGraph().getShapeObj("edge",{shape:"mind-edge"}),getPath(t){return this.getOriginShapeObject(t).getPath(t)},getStyle(t){return{...this.getOriginShapeObject(t).getStyle(t),stroke:"#91D5FF"}}})},function(t,e,n){const r=n(15),i=n(10);function o(t,e,n){const r=t.getGraph(),i=e.getModel(),o=t.getFirstChildrenBySide("left"),a=o[0]&&r.find(o[0].id);return r.add("node",{id:n,parent:e.id,label:"新建节点",side:i.children.length>2?"left":"right",nth:a?r.getNth(a):void 0})}r.registerCommand("append",{enable(t){const e=t.getCurrentPage(),n=e.getSelected();return e.isMind&&1===n.length},getItem(t){const e=t.getCurrentPage(),n=e.getGraph();return this.selectedItemId?n.find(this.selectedItemId):e.getSelected()[0]},execute(t){const e=t.getCurrentPage(),n=e.getGraph(),r=e.getRoot(),i=this.getItem(t),a=i.getModel(),s=a.hierarchy,c=i.getParent();let u;if(i.isRoot)u=o(e,i,this.addItemId);else{const t=n.getNth(i);u=n.add("node",{id:this.addItemId,parent:c.id,side:2===s&&3===r.children.length?"left":a.side,label:"新建节点",nth:"left"===a.side&&2===s?t:t+1})}e.clearSelected(),e.clearActived(),e.setSelected(u,!0),1===this.executeTimes&&(this.selectedItemId=i.id,this.addItemId=u.id,e.beginEditLabel(u))},back(t){const e=t.getCurrentPage();e.getGraph().remove(this.addItemId),e.clearSelected(),e.clearActived(),e.setSelected(this.selectedItemId,!0)},shortcutCodes:["Enter"]}),r.registerCommand("appendChild",{enable:function(t){const e=t.getCurrentPage(),n=e.getSelected();return e.isMind&&n.length>0},getItem(t){const e=t.getCurrentPage(),n=e.getGraph();return this.selectedItemId?n.find(this.selectedItemId):e.getSelected()[0]},execute(t){const e=t.getCurrentPage(),n=e.getGraph(),r=this.getItem(t);let i;i=r.isRoot?o(e,r,this.addItemId):n.add("node",{id:this.addItemId,parent:r.id,label:"新建节点"}),e.clearSelected(),e.clearActived(),e.setSelected(i,!0),1===this.executeTimes&&(this.selectedItemId=r.id,this.addItemId=i.id,e.beginEditLabel(i))},back(t){const e=t.getCurrentPage();e.getGraph().remove(this.addItemId),e.clearSelected(),e.clearActived(),e.setSelected(this.selectedItemId,!0)},shortcutCodes:["Tab"]}),r.registerCommand("moveMindNode",{enable(t){const e=t.getCurrentPage(),n=e.get("panItems");return e.isMind&&n&&n.length>0},execute(t){const e=t.getCurrentPage(),n=e.getGraph(),r=this.newParentId,o=this.newNth,a=this.newSide,s=i.clone(this.model);delete s.shape,delete s.side,n.remove(s.id),i.mix(s,{parent:r,nth:o,side:a});const c=n.add("node",s);e.clearSelected(),e.setSelected(c,!0)},back(t){const e=t.getCurrentPage(),n=e.getGraph(),r=this.originParentId,o=this.originNth,a=this.originSide,s=i.clone(this.model);delete s.shape,delete s.side,n.remove(s.id),i.mix(s,{parent:r,nth:o,side:a});const c=n.add("node",s);e.clearSelected(),e.setSelected(c,!0)}})},function(t,e,n){const r=n(6),i=n(9);t.exports=class extends i{getDefaultCfg(){return{type:"toolbar",container:null}}init(){this._initContainer()}_initContainer(){let t=this.container;if(!t)throw new Error("please set the container for the toolbar !");r.isString(t)&&(t=document.getElementById(t));const e=t.getElementsByClassName("command");this.commands=e}getCommandDoms(){return this.commands}}},function(t,e,n){const r=n(6),i=n(9);t.exports=class extends i{getDefaultCfg(){return{type:"contextmenu",container:null}}init(){this._initContainer()}_initContainer(){let t=this.container;if(!t)throw new Error("please set the container for the tontextmenu !");r.isString(t)&&(t=document.getElementById(t));const e=t.getElementsByClassName("command");t.style.position="absolute",t.style["z-index"]=2,t.style.top="0px",t.style.left="0px",this.commands=e,this.containerDom=t}bindEvent(){const t=this.commands;r.each(t,t=>{r.addEventListener(t,"click",()=>{-1===t.className.indexOf("disable")&&this.hide()})})}switch(t){const e=this.containerDom.getElementsByClassName("menu");r.each(e,e=>{e.dataset.status===t?e.style.display="block":e.style.display="none"})}getCommandDoms(){return this.commands}show(){const t=this.containerDom;this.editor.getCurrentPage().setSignal("preventWheelPan",!0),t.style.display="block"}hide(){const t=this.containerDom;this.editor.getCurrentPage().setSignal("preventWheelPan",!1),t.style.display="none"}move(t,e){const n=this.containerDom,i=r.getBoundingClientRect(n),o=parseFloat(r.getStyle(n,"top")),a=parseFloat(r.getStyle(n,"left"));n.style.left=a+(t-i.left)+"px",n.style.top=o+(e-i.top)+"px"}}},function(t,e,n){const r=n(12);n(124),n(127),t.exports=r},function(t,e,n){n(125),n(126)},function(t,e,n){const{mouseEnterEdge:r,startMove:i,mouseLeaveEdge:o,mouseMoveEdge:a,endMove:s}=n(20);let c,u;n(12).registerBehaviour("bpmnMoveEdgeController",t=>{const e=t.getGraph(),n=e.getRootGroup();let h;function l(){t.set("panItemDelegation",void 0),t.set("panItemStartBox",void 0),t.set("panItemStartPoint",void 0),h=void 0}e.behaviourOn("edge:mouseenter",n=>{!c&&n.item&&(u=n.item,r({graph:e,bpmn:t,ev:n,backUpCursor:!0}))}),e.behaviourOn("edge:mousedown",t=>{const n=t.item;i(e,t),c=n}),e.behaviourOn("mouseup",()=>{c&&(s({graph:e,item:c}),c=void 0)}),e.behaviourOn("mousemove",n=>{c?a(e,c,n):u&&r({graph:e,bpmn:t,ev:{...n,item:u},backUpCursor:!1})}),e.behaviourOn("edge:mouseleave",()=>{c||u&&(o({graph:e,bpmn:t,item:u}),u=void 0)}),e.behaviourOn("node:dragstart",e=>{const r=e.item.getBBox();h=t.getDelegation([r],n),t.set("bpmnNodePanDelegation",h),t.set("bpmnNodePanStartBox",r),t.set("bpmnNodePanStartPoint",{x:e.x,y:e.y}),t.set("bpmnNodePanStartItem",e.item)}),e.behaviourOn("node:dragend",n=>{const r=t.get("bpmnNodePanDelegation"),i=t.get("bpmnNodePanStartPoint"),o=t.get("bpmnNodePanStartItem");o&&o.getEdges().forEach(t=>{e.update(t,{nodeMoved:{item:o.id,start:i,delta:{x:n.x-i.x,y:n.y-i.y}}})}),r.remove(),l()})},["startPanItem","processPanItem","endPanItem"])},function(t,e,n){n(12).registerBehaviour("hoverNodeShowArrowController",t=>{const e=t.getGraph(),n=t.get("arrowController"),{long:r,thickness:i}=n;let o;e.on("node:mouseenter",a=>{if(t.getSignal("panningItem")||t.getSignal("dragEdge"))return;const{topArrow:s,bottomArrow:c,leftArrow:u,rightArrow:h}=n;o=a.item;const l=o.getBBox(),d=e.getDomPoint({x:l.centerX,y:l.minY}),f=e.getDomPoint({x:l.minX,y:l.centerY}),g=e.getDomPoint({x:l.centerX,y:l.maxY}),p=e.getDomPoint({x:l.maxX,y:l.centerY});s.css({top:d.y-(r+10)+"px",left:d.x-i/2+"px",transform:"rotate(-90deg)"}),c.css({top:g.y+10+"px",left:g.x-i/2+"px",transform:"rotate(90deg)"}),u.css({top:f.y-i/2+"px",left:f.x-(r+10)+"px",transform:"rotate(180deg)"}),h.css({top:p.y-i/2+"px",left:p.x+10+"px"}),s.setAttribute("anchorIndex",0),c.setAttribute("anchorIndex",2),u.setAttribute("anchorIndex",3),h.setAttribute("anchorIndex",1),t.showArrowController(o)}),e.behaviourOn("mousemove",({x:e,y:n})=>{if(!o)return;const i=o.getBBox(),a=r+10,s=i.minX-a,c=i.minY-a,u=i.maxX+a,h=i.maxY+a;(eu||n>h)&&(t.hideArrowController(),o=void 0)})})},function(t,e,n){n(128);n(12).registerNode("bpmn-base",{anchor:null})},function(t,e,n){"use strict";n.r(e);var r=n(12),i=n.n(r),o=n(0);function a({bbox:t,shape:e="ROUNDED_RECT",point:n,vertical:r}){r=!!r;const i={true:"x",false:"y"},o={true:"minX",false:"minY"},a={true:"maxX",false:"maxY"};switch(e){case"ROUNDED_RECT":if(n[i[r]]>=t[o[r]]+4&&n[i[r]]<=t[a[r]]-4)return{added:void 0,joint:{x:r?n.x:n.x>t.centerX?t.maxX:t.minX,y:r?n.y>t.centerY?t.maxY:t.minY:n.y}};if(n[i[r]]>=t[o[r]]&&n[i[r]]t.centerX?t.maxX-4+Math.sqrt(16-Math.pow(t.minY+4-n.y,2)):t.minX+4-Math.sqrt(16-Math.pow(t.minY+4-n.y,2)),y:r?n.y>t.centerY?t.maxY-4+Math.sqrt(16-Math.pow(t.minX+4-n.x,2)):t.minY+4-Math.sqrt(16-Math.pow(t.minX+4-n.x,2)):n.y}};if(n[i[r]]>=t[a[r]]-4&&n[i[r]]<=t[a[r]])return{added:void 0,joint:{x:r?n.x:n.x>t.centerX?t.maxX-4+Math.sqrt(16-Math.pow(n.y-t.maxY+4,2)):t.minX+4-Math.sqrt(16-Math.pow(n.y-t.maxY+4,2)),y:r?n.y>t.centerY?t.maxY-4+Math.sqrt(16-Math.pow(n.x-t.maxX+4,2)):t.minY+4-Math.sqrt(16-Math.pow(n.x-t.maxX+4,2)):n.y}};break;case"CIRCLE":if(n[i[r]]>=t[o[r]]&&n[i[r]]<=t[a[r]])return{added:void 0,joint:{x:r?n.x:n.x>t.centerX?t.centerX+Math.sqrt(16-Math.pow(t.centerY-n.y)):t.centerX-Math.sqrt(16-Math.pow(t.centerY-n.y)),y:r?n.y>t.centerY?t.centerY+Math.sqrt(16-Math.pow(t.centerX-n.x)):t.centerY-Math.sqrt(16-Math.pow(t.centerX-n.x)):n.y}};break;case"RHOMEBUS":if(n[i[r]]>=t[o[r]]&&n[i[r]]<=t[a[r]])return{added:void 0,joint:{x:r?n.x:n.x>t.centerX?n.yt.centerY?n.x=t[o[r]]&&n[i[r]]<=t[a[r]])return{added:void 0,joint:{x:r?n.x:n.x>t.centerX?t.maxX:t.minX,y:r?n.y>t.centerY?t.maxY:t.minY:n.y}}}return r?n.x=150&&(n.helpLine=void 0,n.helpLineTime=void 0),n.helpLine&&e.addShape("path",{attrs:{path:[["M",n.helpLine[0].x,n.helpLine[0].y],["L",n.helpLine[1].x,n.helpLine[1].y]],lineAppendWidth:8,lineWidth:1,strokeOpacity:.92,stroke:"rgba(255,139,48)"}}),s}})}])})); \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/ggeditor/helpers/track.js b/saga/seata-saga-statemachine-designer/ggeditor/helpers/track.js new file mode 100644 index 0000000..10d8974 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/helpers/track.js @@ -0,0 +1,34 @@ +import Global from '@common/Global'; +import { toQueryString } from '@utils'; + +const BASE_URL = 'http://gm.mmstat.com/fsp.1.1'; + +const track = (options) => { + const trackable = Global.get('trackable'); + const version = Global.get('version'); + + if (!trackable) { + return; + } + + const { location, navigator } = window; + const image = new Image(); + const params = toQueryString({ + pid: 'ggeditor', + code: '11', + msg: 'syslog', + page: `${location.protocol}//${location.host}${location.pathname}`, + hash: location.hash, + ua: navigator.userAgent, + rel: version, + ...options, + }); + + image.src = `${BASE_URL}?${params}`; +}; + +export default (options) => { + setTimeout(() => { + track(options); + }, 1000); +}; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/index.js b/saga/seata-saga-statemachine-designer/ggeditor/index.js new file mode 100644 index 0000000..1c4ed90 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/index.js @@ -0,0 +1,63 @@ +import Flow from '@components/Flow'; +import Mind from '@components/Mind'; +import Koni from '@components/Koni'; +import { + RegisterNode, + RegisterEdge, + RegisterGroup, + RegisterGuide, + RegisterCommand, + RegisterBehaviour, +} from '@components/Register'; +import Command from '@components/Command'; +import Minimap from '@components/Minimap'; +import ContextMenu, { + NodeMenu, + EdgeMenu, + GroupMenu, + MultiMenu, + CanvasMenu, +} from '@components/ContextMenu'; +import Toolbar from '@components/Toolbar'; +import ItemPanel, { Item } from '@components/ItemPanel'; +import DetailPanel, { + NodePanel, + EdgePanel, + GroupPanel, + MultiPanel, + CanvasPanel, +} from '@components/DetailPanel'; +import withPropsAPI from '@common/context/PropsAPIContext/withPropsAPI'; +import GGEditor from '@components/GGEditor'; + +export { + Flow, + Mind, + Koni, + RegisterNode, + RegisterEdge, + RegisterGroup, + RegisterGuide, + RegisterCommand, + RegisterBehaviour, + Command, + Minimap, + NodeMenu, + EdgeMenu, + GroupMenu, + MultiMenu, + CanvasMenu, + ContextMenu, + Toolbar, + Item, + ItemPanel, + NodePanel, + EdgePanel, + GroupPanel, + MultiPanel, + CanvasPanel, + DetailPanel, + withPropsAPI, +}; + +export default GGEditor; diff --git a/saga/seata-saga-statemachine-designer/ggeditor/utils/index.js b/saga/seata-saga-statemachine-designer/ggeditor/utils/index.js new file mode 100644 index 0000000..9beea39 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/ggeditor/utils/index.js @@ -0,0 +1,14 @@ +import merge from 'lodash/merge'; +import pick from 'lodash/pick'; +import uniqueId from 'lodash/uniqueId'; +import upperFirst from 'lodash/upperFirst'; + +const toQueryString = obj => Object.keys(obj).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`).join('&'); + +export { + merge, + pick, + toQueryString, + uniqueId, + upperFirst, +}; diff --git a/saga/seata-saga-statemachine-designer/index.html b/saga/seata-saga-statemachine-designer/index.html new file mode 100644 index 0000000..e42c917 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/index.html @@ -0,0 +1,22 @@ + + + + + + + + Seata Saga StateMachine Designer + + + + +
    + + + + + + + + + \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/package-lock.json b/saga/seata-saga-statemachine-designer/package-lock.json new file mode 100644 index 0000000..9dc55ed --- /dev/null +++ b/saga/seata-saga-statemachine-designer/package-lock.json @@ -0,0 +1,10647 @@ +{ + "name": "seata-saga-statemachine-designer", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@antv/attr": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@antv/attr/-/attr-0.0.7.tgz", + "integrity": "sha512-dgvJ2j6Sn7of8AET9ykTseS8mjwisLcAGf/UCkEaMLCduCr6hQtwAEBIZnEpk0b04QlD5pu0kqV93B1ItnvCPw==", + "requires": { + "@antv/util": "~1.2.5" + }, + "dependencies": { + "@antv/util": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-1.2.5.tgz", + "integrity": "sha512-yz1AjXSEjNu9O5kK9pqKq69f/Iliu/IA3XXljUcfrlbUtmUJ0CH1tB5I60vPqfaKaUPhz+/35K+56yqaCsGmqA==", + "requires": { + "@antv/gl-matrix": "^2.7.1" + } + } + } + }, + "@antv/component": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@antv/component/-/component-0.0.9.tgz", + "integrity": "sha512-AcI6oG0Ot9svKieA3AowQuGmwsIjQpC2XJv71FRua/3b0IaWnF3K93vyJnmsWej+CQnXPE68JZaIjdgtAcgTwg==", + "requires": { + "@antv/attr": "~0.0.7", + "@antv/g": "~3.2.2", + "@antv/util": "~1.2.5", + "wolfy87-eventemitter": "~5.1.0" + }, + "dependencies": { + "@antv/g": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@antv/g/-/g-3.2.2.tgz", + "integrity": "sha512-mBuFnoWS6zIRy+MhpGDJxq1tHJj0o1mp0ifWPRiIFO3rlZLcNHf0D/Ww+UoC5+d8GZTz+6JqIzmxCZ3EZ4LmdQ==", + "requires": { + "@antv/gl-matrix": "~2.7.1", + "@antv/util": "~1.2.3", + "d3-ease": "~1.0.3", + "d3-interpolate": "~1.1.5", + "d3-timer": "~1.0.6", + "wolfy87-eventemitter": "~5.1.0" + } + }, + "@antv/util": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-1.2.5.tgz", + "integrity": "sha512-yz1AjXSEjNu9O5kK9pqKq69f/Iliu/IA3XXljUcfrlbUtmUJ0CH1tB5I60vPqfaKaUPhz+/35K+56yqaCsGmqA==", + "requires": { + "@antv/gl-matrix": "^2.7.1" + } + }, + "wolfy87-eventemitter": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.1.0.tgz", + "integrity": "sha1-NcGsDdGsDBXjXZgVCPwiCEoToBE=" + } + } + }, + "@antv/g": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@antv/g/-/g-3.4.6.tgz", + "integrity": "sha512-S7eXmMZSZmIbm+9E5zMHLtpDDa9JmfUmm7GJg1zXg2Ow8F8i3ekTZsYkgk8TA2ZPTGWfcmkSTifyqctZmdmqFw==", + "requires": { + "@antv/gl-matrix": "~2.7.1", + "@antv/util": "~1.3.1", + "d3-ease": "~1.0.3", + "d3-interpolate": "~1.1.5", + "d3-timer": "~1.0.6" + } + }, + "@antv/g6": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@antv/g6/-/g6-2.2.6.tgz", + "integrity": "sha512-xxuXYjxw7o97PRo94e3pXHbsdHYdd+oKzy20ig91F61Bb7ddlHZg0I6ReU+skfS83cffqozgS+eZ9GWAUkS9UQ==", + "requires": { + "@antv/component": "~0.0.4", + "@antv/g": "^3.3.0", + "@antv/hierarchy": "~0.3.13", + "@antv/scale": "^0.0.1", + "@antv/util": "^1.2.0", + "d3": "^5.4.0", + "d3-sankey": "^0.7.1", + "d3-svg-legend": "^2.25.6", + "dagre": "~0.8.2", + "dom-to-image": "^2.6.0", + "lodash": "~4.17.4", + "wolfy87-eventemitter": "~5.2.4" + } + }, + "@antv/gl-matrix": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@antv/gl-matrix/-/gl-matrix-2.7.1.tgz", + "integrity": "sha512-oOWcVNlpELIKi9x+Mm1Vwbz8pXfkbJKykoCIOJ/dNK79hSIANbpXJ5d3Rra9/wZqK6MC961B7sybFhPlLraT3Q==" + }, + "@antv/hierarchy": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.3.15.tgz", + "integrity": "sha512-TxgrQrNayVLgimfbWti+QIMVEEt+Pc8dodMC4ypMSAsJ6Yj8JXmcibgego7j7dFRqnlzyUdaiNCQUMBgl2cQvQ==", + "requires": { + "@antv/util": "^1.2.4" + } + }, + "@antv/scale": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@antv/scale/-/scale-0.0.1.tgz", + "integrity": "sha512-SZ5nRe57tYq1dOJLPwwc+8iQFeHXXMTq3Me9UKYmnSvy9uh0GMoe0OwqkwJuFDsGJjJ4iyM/JNuvb0mwCC+Nhw==", + "requires": { + "@antv/util": "~1.0.6", + "fecha": "~2.3.3" + }, + "dependencies": { + "@antv/util": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-1.0.12.tgz", + "integrity": "sha512-lRQ98e4g6qHgZ78ak5HJq6tCQRfofcdIi5H7mXIubp2mpfQHaez2eMKxmPAvHTyD3At74gNP8qjFdzHsPcXsXA==" + } + } + }, + "@antv/util": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-1.3.1.tgz", + "integrity": "sha512-cbUta0hIJrKEaW3eKoGarz3Ita+9qUPF2YzTj8A6wds/nNiy20G26ztIWHU+5ThLc13B1n5Ik52LbaCaeg9enA==", + "requires": { + "@antv/gl-matrix": "^2.7.1" + } + }, + "@babel/cli": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/cli/download/@babel/cli-7.10.4.tgz?cache=0&sync_timestamp=1593523627481&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcli%2Fdownload%2F%40babel%2Fcli-7.10.4.tgz", + "integrity": "sha1-ujitbQtLdypnsQaTS3wz1lYDGJY=", + "dev": true, + "requires": { + "chokidar": "^2.1.8", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.13", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/commander/download/commander-4.1.1.tgz?cache=0&sync_timestamp=1592632033071&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcommander%2Fdownload%2Fcommander-4.1.1.tgz", + "integrity": "sha1-n9YCvZNilOnp70aj9NaWQESxgGg=", + "dev": true + } + } + }, + "@babel/code-frame": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", + "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/compat-data": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/compat-data/download/@babel/compat-data-7.10.4.tgz?cache=0&sync_timestamp=1593521048204&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcompat-data%2Fdownload%2F%40babel%2Fcompat-data-7.10.4.tgz", + "integrity": "sha1-cGpkhO5vkQtxm2lqkZT42n16wkE=", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.12.2", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.12.2.tgz?cache=0&sync_timestamp=1593190408386&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.12.2.tgz", + "integrity": "sha1-dmU9fkxXyqihooUT4vThl9wRpxE=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001088", + "electron-to-chromium": "^1.3.483", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001093", + "resolved": "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001093.tgz?cache=0&sync_timestamp=1593641273200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001093.tgz", + "integrity": "sha1-gz6A9ksaBFXLzu0qSjuvGeSr0xI=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.484", + "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.484.tgz?cache=0&sync_timestamp=1593660932805&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.484.tgz", + "integrity": "sha1-dfWh7uX+MWh1i3ws83Wuc8HM9eY=", + "dev": true + }, + "node-releases": { + "version": "1.1.58", + "resolved": "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.58.tgz?cache=0&sync_timestamp=1591162286391&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.58.tgz", + "integrity": "sha1-juIO7zD6YOUnVfzAlC3vWnNP6TU=", + "dev": true + } + } + }, + "@babel/core": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/core/download/@babel/core-7.10.4.tgz?cache=0&sync_timestamp=1593521230897&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcore%2Fdownload%2F%40babel%2Fcore-7.10.4.tgz", + "integrity": "sha1-eA6Lg+SWFS+N199jiSsuBSvx1R0=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz?cache=0&sync_timestamp=1593522826253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.10.4.tgz", + "integrity": "sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.10.4.tgz", + "integrity": "sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/generator/download/@babel/generator-7.10.4.tgz", + "integrity": "sha1-5J7u2f4RS2L6WxgYVqQ6XjL18kM=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-annotate-as-pure/download/@babel/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha1-W/DUlaP3V6w72ki1vzs7ownHK6M=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-builder-binary-assignment-operator-visitor/download/@babel/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha1-uwt18xv5jL+f8UPBrleLhydK4aM=", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-builder-react-jsx/download/@babel/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha1-gJXN2/+Fjm+pwyba7lSi8nMsHV0=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-builder-react-jsx-experimental/download/@babel/helper-builder-react-jsx-experimental-7.10.4.tgz?cache=0&sync_timestamp=1593522974577&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-builder-react-jsx-experimental%2Fdownload%2F%40babel%2Fhelper-builder-react-jsx-experimental-7.10.4.tgz", + "integrity": "sha1-0P+4dRhNdJxj/+H09lvhUUPsMi0=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-compilation-targets/download/@babel/helper-compilation-targets-7.10.4.tgz?cache=0&sync_timestamp=1593521085687&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-compilation-targets%2Fdownload%2F%40babel%2Fhelper-compilation-targets-7.10.4.tgz", + "integrity": "sha1-gEro4/BDdmB8x5G51H1UAnYzK9I=", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.12.2", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.12.2.tgz?cache=0&sync_timestamp=1593190408386&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.12.2.tgz", + "integrity": "sha1-dmU9fkxXyqihooUT4vThl9wRpxE=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001088", + "electron-to-chromium": "^1.3.483", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001093", + "resolved": "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001093.tgz?cache=0&sync_timestamp=1593641273200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001093.tgz", + "integrity": "sha1-gz6A9ksaBFXLzu0qSjuvGeSr0xI=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.484", + "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.484.tgz?cache=0&sync_timestamp=1593660932805&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.484.tgz", + "integrity": "sha1-dfWh7uX+MWh1i3ws83Wuc8HM9eY=", + "dev": true + }, + "node-releases": { + "version": "1.1.58", + "resolved": "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.58.tgz?cache=0&sync_timestamp=1591162286391&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.58.tgz", + "integrity": "sha1-juIO7zD6YOUnVfzAlC3vWnNP6TU=", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-create-class-features-plugin/download/@babel/helper-create-class-features-plugin-7.10.4.tgz", + "integrity": "sha1-LUAV0BNr0xQQOnDYSnGD5LNEo1U=", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-create-regexp-features-plugin/download/@babel/helper-create-regexp-features-plugin-7.10.4.tgz?cache=0&sync_timestamp=1593522952175&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-create-regexp-features-plugin%2Fdownload%2F%40babel%2Fhelper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha1-/dYNiFJGWaC2lZwFeZJeQlcU87g=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-define-map/download/@babel/helper-define-map-7.10.4.tgz", + "integrity": "sha1-8DeteUJk9yntoYifTuIQuHCZkJI=", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-explode-assignable-expression/download/@babel/helper-explode-assignable-expression-7.10.4.tgz?cache=0&sync_timestamp=1593522841702&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-explode-assignable-expression%2Fdownload%2F%40babel%2Fhelper-explode-assignable-expression-7.10.4.tgz", + "integrity": "sha1-QKHNkXv/Eoj2malKdbN6Gi29jHw=", + "dev": true, + "requires": { + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-function-name/download/@babel/helper-function-name-7.10.4.tgz?cache=0&sync_timestamp=1593521218775&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-function-name%2Fdownload%2F%40babel%2Fhelper-function-name-7.10.4.tgz", + "integrity": "sha1-0tOyDFmtjEcRL6fSqUvAnV74Lxo=", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-get-function-arity/download/@babel/helper-get-function-arity-7.10.4.tgz?cache=0&sync_timestamp=1593522827189&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-get-function-arity%2Fdownload%2F%40babel%2Fhelper-get-function-arity-7.10.4.tgz", + "integrity": "sha1-mMHL6g4jMvM/mkZhuM4VBbLBm6I=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-hoist-variables/download/@babel/helper-hoist-variables-7.10.4.tgz?cache=0&sync_timestamp=1593521259807&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-hoist-variables%2Fdownload%2F%40babel%2Fhelper-hoist-variables-7.10.4.tgz", + "integrity": "sha1-1JsAHR1aaMpeZgTdoBpil/fJOB4=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-member-expression-to-functions/download/@babel/helper-member-expression-to-functions-7.10.4.tgz?cache=0&sync_timestamp=1593522926997&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-member-expression-to-functions%2Fdownload%2F%40babel%2Fhelper-member-expression-to-functions-7.10.4.tgz", + "integrity": "sha1-fNBLV9/Pgvzprq59TkRS+jG4x8Q=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-module-imports/download/@babel/helper-module-imports-7.10.4.tgz", + "integrity": "sha1-TFxUvgS9MWcKc4J5fXW5+i5bViA=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-module-transforms/download/@babel/helper-module-transforms-7.10.4.tgz", + "integrity": "sha1-yh8B/bhOSMJNdQa7gYyWHx2ogF0=", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-optimise-call-expression/download/@babel/helper-optimise-call-expression-7.10.4.tgz?cache=0&sync_timestamp=1593522827576&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-optimise-call-expression%2Fdownload%2F%40babel%2Fhelper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha1-UNyWQT1ZT5lad5BZBbBYk813lnM=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-plugin-utils/download/@babel/helper-plugin-utils-7.10.4.tgz?cache=0&sync_timestamp=1593521123680&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-plugin-utils%2Fdownload%2F%40babel%2Fhelper-plugin-utils-7.10.4.tgz", + "integrity": "sha1-L3WoMSadT2d95JmG3/WZJ1M883U=", + "dev": true + }, + "@babel/helper-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-regex/download/@babel/helper-regex-7.10.4.tgz?cache=0&sync_timestamp=1593521091742&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-regex%2Fdownload%2F%40babel%2Fhelper-regex-7.10.4.tgz", + "integrity": "sha1-WbNz2q80WOV0feznG7r0X5Z2r20=", + "dev": true, + "requires": { + "lodash": "^4.17.13" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-remap-async-to-generator/download/@babel/helper-remap-async-to-generator-7.10.4.tgz?cache=0&sync_timestamp=1593521228698&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-remap-async-to-generator%2Fdownload%2F%40babel%2Fhelper-remap-async-to-generator-7.10.4.tgz", + "integrity": "sha1-/Oi+pOlpC76SMFbe0h5UtOi2jtU=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-replace-supers/download/@babel/helper-replace-supers-7.10.4.tgz", + "integrity": "sha1-1YXNk4jqBuYDHkzUS2cTy+rZ5s8=", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-simple-access/download/@babel/helper-simple-access-7.10.4.tgz?cache=0&sync_timestamp=1593521217867&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-simple-access%2Fdownload%2F%40babel%2Fhelper-simple-access-7.10.4.tgz", + "integrity": "sha1-D1zNopRSd6KnotOoIeFTle3PNGE=", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-split-export-declaration/download/@babel/helper-split-export-declaration-7.10.4.tgz?cache=0&sync_timestamp=1593522826673&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-split-export-declaration%2Fdownload%2F%40babel%2Fhelper-split-export-declaration-7.10.4.tgz", + "integrity": "sha1-LHBXbqo7VgmyTLmdsoiMw/xCUdE=", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-validator-identifier/download/@babel/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha1-p4x6clHgH2FlEtMbEK3PUq2l4NI=", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helper-wrap-function/download/@babel/helper-wrap-function-7.10.4.tgz?cache=0&sync_timestamp=1593522949000&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-wrap-function%2Fdownload%2F%40babel%2Fhelper-wrap-function-7.10.4.tgz", + "integrity": "sha1-im9wHqsP8592W1oc/vQJmQ5iS4c=", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/helpers/download/@babel/helpers-7.10.4.tgz", + "integrity": "sha1-Kr6w1yGv98Cpc3a54fb2XXpHUEQ=", + "dev": true, + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/parser/download/@babel/parser-7.10.4.tgz?cache=0&sync_timestamp=1593521035480&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fparser%2Fdownload%2F%40babel%2Fparser-7.10.4.tgz", + "integrity": "sha1-nu3yfhmY2Hc5+1AopRIFV8BqGmQ=", + "dev": true + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-async-generator-functions/download/@babel/plugin-proposal-async-generator-functions-7.10.4.tgz?cache=0&sync_timestamp=1593521231856&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-async-generator-functions%2Fdownload%2F%40babel%2Fplugin-proposal-async-generator-functions-7.10.4.tgz", + "integrity": "sha1-S2Wrs9m6zGxleqpBPlZpb58XD8Y=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-class-properties/download/@babel/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha1-ozv2Mto5ClnHqMVwBF0RFc13iAc=", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-dynamic-import/download/@babel/plugin-proposal-dynamic-import-7.10.4.tgz?cache=0&sync_timestamp=1593521117836&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-dynamic-import%2Fdownload%2F%40babel%2Fplugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha1-uleibLmLN3QenVvKG4sN34KR8X4=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-json-strings/download/@babel/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha1-WT5ZxjUoFgIzvTIbGuvgggwjQds=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-nullish-coalescing-operator/download/@babel/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz?cache=0&sync_timestamp=1593522818985&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-nullish-coalescing-operator%2Fdownload%2F%40babel%2Fplugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha1-AqfpYfwy5tWy2wZJ4Bv4Dd7n4Eo=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-numeric-separator/download/@babel/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha1-zhWQ/wplrRKXCmCdeIVemkwa7wY=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-object-rest-spread/download/@babel/plugin-proposal-object-rest-spread-7.10.4.tgz", + "integrity": "sha1-UBKawha5pqVbOFP92SPnS/VTpMA=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-catch-binding/download/@babel/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha1-Mck4MJ0kp4pJ1o/av/qoY3WFVN0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-optional-chaining/download/@babel/plugin-proposal-optional-chaining-7.10.4.tgz?cache=0&sync_timestamp=1593521131942&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-optional-chaining%2Fdownload%2F%40babel%2Fplugin-proposal-optional-chaining-7.10.4.tgz", + "integrity": "sha1-dQ8SVekwofgtjN3kUDH4Gg0K3/c=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-private-methods/download/@babel/plugin-proposal-private-methods-7.10.4.tgz?cache=0&sync_timestamp=1593522940799&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-private-methods%2Fdownload%2F%40babel%2Fplugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha1-sWDZcrj9ulx9ERoUX8jEIfwqaQk=", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-proposal-unicode-property-regex/download/@babel/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha1-RIPNpTBBzjQTt/4vAAImZd36p10=", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-async-generators/download/@babel/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha1-qYP7Gusuw/btBCohD2QOkOeG/g0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-class-properties/download/@babel/plugin-syntax-class-properties-7.10.4.tgz?cache=0&sync_timestamp=1593521086484&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-class-properties%2Fdownload%2F%40babel%2Fplugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha1-ZkTmoLqlWmH54yMfbJ7rbuRsEkw=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-dynamic-import/download/@babel/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha1-Yr+Ysto80h1iYVT8lu5bPLaOrLM=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-json-strings/download/@babel/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha1-AcohtmjNghjJ5kDLbdiMVBKyyWo=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-jsx/download/@babel/plugin-syntax-jsx-7.10.4.tgz?cache=0&sync_timestamp=1593522939386&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-jsx%2Fdownload%2F%40babel%2Fplugin-syntax-jsx-7.10.4.tgz", + "integrity": "sha1-Oauq48v3EMQ3PYQpSE5rohNAFmw=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-nullish-coalescing-operator/download/@babel/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha1-Fn7XA2iIYIH3S1w2xlqIwDtm0ak=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-numeric-separator/download/@babel/plugin-syntax-numeric-separator-7.10.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-numeric-separator%2Fdownload%2F%40babel%2Fplugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha1-ubBws+M1cM2f0Hun+pHA3Te5r5c=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-object-rest-spread/download/@babel/plugin-syntax-object-rest-spread-7.8.3.tgz?cache=0&sync_timestamp=1578950070697&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-syntax-object-rest-spread%2Fdownload%2F%40babel%2Fplugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha1-YOIl7cvZimQDMqLnLdPmbxr1WHE=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-optional-catch-binding/download/@babel/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha1-YRGiZbz7Ag6579D9/X0mQCue1sE=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-optional-chaining/download/@babel/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha1-T2nCq5UWfgGAzVM2YT+MV4j31Io=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-syntax-top-level-await/download/@babel/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha1-S764kXtU/PdoNk4KgfVg4zo+9X0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-arrow-functions/download/@babel/plugin-transform-arrow-functions-7.10.4.tgz?cache=0&sync_timestamp=1593522807583&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-arrow-functions%2Fdownload%2F%40babel%2Fplugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha1-4ilg135pfHT0HFAdRNc9v4pqZM0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-async-to-generator/download/@babel/plugin-transform-async-to-generator-7.10.4.tgz?cache=0&sync_timestamp=1593522851748&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-async-to-generator%2Fdownload%2F%40babel%2Fplugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha1-QaUBfknrbzzak5KlHu8pQFskWjc=", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-block-scoped-functions/download/@babel/plugin-transform-block-scoped-functions-7.10.4.tgz?cache=0&sync_timestamp=1593521910347&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-block-scoped-functions%2Fdownload%2F%40babel%2Fplugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha1-GvpZV0T3XkOpGvc7DZmOz+Trwug=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-block-scoping/download/@babel/plugin-transform-block-scoping-7.10.4.tgz?cache=0&sync_timestamp=1593522918894&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-block-scoping%2Fdownload%2F%40babel%2Fplugin-transform-block-scoping-7.10.4.tgz", + "integrity": "sha1-pnDRNku1AZpiG56iABSCh21zR4c=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-classes/download/@babel/plugin-transform-classes-7.10.4.tgz?cache=0&sync_timestamp=1593521236444&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-classes%2Fdownload%2F%40babel%2Fplugin-transform-classes-7.10.4.tgz", + "integrity": "sha1-QFE2rys+IYvEoZJiKLyRerGgrcc=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-computed-properties/download/@babel/plugin-transform-computed-properties-7.10.4.tgz?cache=0&sync_timestamp=1593522921161&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-computed-properties%2Fdownload%2F%40babel%2Fplugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha1-ne2DqBboLe0o1S1LTsvdgQzfwOs=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-destructuring/download/@babel/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha1-cN3Ss9G+qD0BUJ6bsl3bOnT8heU=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-dotall-regex/download/@babel/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha1-RpwgYhBcHragQOr0+sS0iAeDle4=", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-duplicate-keys/download/@babel/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha1-aX5Qyf7hQ4D+hD0fMGspVhdDHkc=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-exponentiation-operator/download/@babel/plugin-transform-exponentiation-operator-7.10.4.tgz?cache=0&sync_timestamp=1593521230232&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-exponentiation-operator%2Fdownload%2F%40babel%2Fplugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha1-WuM4xX+M9AAb2zVgeuZrktZlry4=", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-for-of/download/@babel/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha1-wIiS6IGdOl2ykDGxFa9RHbv+uuk=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-function-name/download/@babel/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha1-akZ4gOD8ljhRS6NpERgR3b4mRLc=", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-literals/download/@babel/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha1-n0K6CEEQChNfInEtDjkcRi9XHzw=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-member-expression-literals/download/@babel/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha1-sexE/PGVr8uNssYs2OVRyIG6+Lc=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-modules-amd/download/@babel/plugin-transform-modules-amd-7.10.4.tgz", + "integrity": "sha1-y0B8aLhi5MHROi/HOMfsXtdfxSA=", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-modules-commonjs/download/@babel/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha1-ZmZ8Pu2h6/eJbUHx8WsXEFovvKA=", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-modules-systemjs/download/@babel/plugin-transform-modules-systemjs-7.10.4.tgz?cache=0&sync_timestamp=1593522937122&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-systemjs%2Fdownload%2F%40babel%2Fplugin-transform-modules-systemjs-7.10.4.tgz", + "integrity": "sha1-j1dq/ZQ6wveJs13tCmMS+SnGM/k=", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-modules-umd/download/@babel/plugin-transform-modules-umd-7.10.4.tgz?cache=0&sync_timestamp=1593522937615&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-modules-umd%2Fdownload%2F%40babel%2Fplugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha1-moSB/oG4JGVLOgtl2j34nz0hg54=", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-named-capturing-groups-regex/download/@babel/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha1-eLTZeIELbzvPA/njGPL8DtQa7LY=", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-new-target/download/@babel/plugin-transform-new-target-7.10.4.tgz?cache=0&sync_timestamp=1593522495673&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-new-target%2Fdownload%2F%40babel%2Fplugin-transform-new-target-7.10.4.tgz", + "integrity": "sha1-kJfXU8t7Aky3OBo7LlLpUTqcaIg=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-object-super/download/@babel/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha1-1xRsTROUM+emUm+IjGZ+MUoJOJQ=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-parameters/download/@babel/plugin-transform-parameters-7.10.4.tgz?cache=0&sync_timestamp=1593522848395&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-parameters%2Fdownload%2F%40babel%2Fplugin-transform-parameters-7.10.4.tgz", + "integrity": "sha1-e00TfIfqetwqDz6/UyZocdqm/O0=", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-property-literals/download/@babel/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha1-9v5UtlkDUimHhbg+3YFdIUxC48A=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-display-name/download/@babel/plugin-transform-react-display-name-7.10.4.tgz?cache=0&sync_timestamp=1593522925495&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-display-name%2Fdownload%2F%40babel%2Fplugin-transform-react-display-name-7.10.4.tgz", + "integrity": "sha1-tXlfTj4xQEGcNhG3oqODK5rvMo0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-jsx/download/@babel/plugin-transform-react-jsx-7.10.4.tgz?cache=0&sync_timestamp=1593522836746&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-jsx%2Fdownload%2F%40babel%2Fplugin-transform-react-jsx-7.10.4.tgz", + "integrity": "sha1-ZzyfkTlIdkpEIWg7K+8pNpaP3fI=", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-jsx-development/download/@babel/plugin-transform-react-jsx-development-7.10.4.tgz?cache=0&sync_timestamp=1593521335160&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-jsx-development%2Fdownload%2F%40babel%2Fplugin-transform-react-jsx-development-7.10.4.tgz", + "integrity": "sha1-bskPJEOUYEYjiA4V68PDTDViWLo=", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx-experimental": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-self": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-jsx-self/download/@babel/plugin-transform-react-jsx-self-7.10.4.tgz?cache=0&sync_timestamp=1593522826499&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-jsx-self%2Fdownload%2F%40babel%2Fplugin-transform-react-jsx-self-7.10.4.tgz", + "integrity": "sha1-zTAaX+2JiMGC7QudVem9bbC9k2k=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-jsx-source": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-jsx-source/download/@babel/plugin-transform-react-jsx-source-7.10.4.tgz?cache=0&sync_timestamp=1593522826500&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-jsx-source%2Fdownload%2F%40babel%2Fplugin-transform-react-jsx-source-7.10.4.tgz", + "integrity": "sha1-hrrw/Mz+WAhOBkRqgIWOHero8pE=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-react-pure-annotations/download/@babel/plugin-transform-react-pure-annotations-7.10.4.tgz?cache=0&sync_timestamp=1593521328616&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-react-pure-annotations%2Fdownload%2F%40babel%2Fplugin-transform-react-pure-annotations-7.10.4.tgz", + "integrity": "sha1-Pu+7c9uUr7wHXwl1I+RFNUocZQE=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-regenerator/download/@babel/plugin-transform-regenerator-7.10.4.tgz?cache=0&sync_timestamp=1593521089707&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-regenerator%2Fdownload%2F%40babel%2Fplugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha1-IBXlnYOQdOdoON4hWdtCGWb9i2M=", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-reserved-words/download/@babel/plugin-transform-reserved-words-7.10.4.tgz?cache=0&sync_timestamp=1593522496981&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-reserved-words%2Fdownload%2F%40babel%2Fplugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha1-jyaCvNzvntMn4bCGFYXXAT+KVN0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-runtime/download/@babel/plugin-transform-runtime-7.10.4.tgz?cache=0&sync_timestamp=1593522940963&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-runtime%2Fdownload%2F%40babel%2Fplugin-transform-runtime-7.10.4.tgz", + "integrity": "sha1-WU+1NFPqG28HeczrSM4HGKRH/rc=", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-shorthand-properties/download/@babel/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha1-n9Jexc3VVbt/Rz5ebuHJce7eTdY=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-spread/download/@babel/plugin-transform-spread-7.10.4.tgz?cache=0&sync_timestamp=1593522927458&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-spread%2Fdownload%2F%40babel%2Fplugin-transform-spread-7.10.4.tgz", + "integrity": "sha1-TiyF6g1quu4bJNz7uuQm/o1nTP8=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-sticky-regex/download/@babel/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha1-jziJ7oZXWBEwop2cyR18c7fEoo0=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-template-literals/download/@babel/plugin-transform-template-literals-7.10.4.tgz?cache=0&sync_timestamp=1593522854232&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-template-literals%2Fdownload%2F%40babel%2Fplugin-transform-template-literals-7.10.4.tgz", + "integrity": "sha1-5jdUB7MPy3/P27o7uY7z6dNt97w=", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-typeof-symbol/download/@babel/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha1-lQnxp+7DHE7b/+E3wWzDP/C8W/w=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-unicode-escapes/download/@babel/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha1-/q5SM5HHZR3awRXa4KnQaFeJIAc=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/plugin-transform-unicode-regex/download/@babel/plugin-transform-unicode-regex-7.10.4.tgz?cache=0&sync_timestamp=1593522855498&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-transform-unicode-regex%2Fdownload%2F%40babel%2Fplugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha1-5W1x+SgvrG2wnIJ0IFVXbV5tgKg=", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/polyfill": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/polyfill/download/@babel/polyfill-7.10.4.tgz", + "integrity": "sha1-kV5b/mFJCsAZkAjjXKnX0VGo5Fo=", + "dev": true, + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-2.6.11.tgz?cache=0&sync_timestamp=1586450269267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-2.6.11.tgz", + "integrity": "sha1-OIMUafmSK97Y7iHJ3EaYXgOZMIw=", + "dev": true + } + } + }, + "@babel/preset-env": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/preset-env/download/@babel/preset-env-7.10.4.tgz?cache=0&sync_timestamp=1593521234560&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpreset-env%2Fdownload%2F%40babel%2Fpreset-env-7.10.4.tgz", + "integrity": "sha1-+/V/moA6/Zf08y5PeYu2Lksr718=", + "dev": true, + "requires": { + "@babel/compat-data": "^7.10.4", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.10.4", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.10.4", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.10.4", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + }, + "dependencies": { + "browserslist": { + "version": "4.12.2", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.12.2.tgz?cache=0&sync_timestamp=1593190408386&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.12.2.tgz", + "integrity": "sha1-dmU9fkxXyqihooUT4vThl9wRpxE=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001088", + "electron-to-chromium": "^1.3.483", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001093", + "resolved": "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001093.tgz?cache=0&sync_timestamp=1593641273200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001093.tgz", + "integrity": "sha1-gz6A9ksaBFXLzu0qSjuvGeSr0xI=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.484", + "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.484.tgz?cache=0&sync_timestamp=1593660932805&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.484.tgz", + "integrity": "sha1-dfWh7uX+MWh1i3ws83Wuc8HM9eY=", + "dev": true + }, + "node-releases": { + "version": "1.1.58", + "resolved": "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.58.tgz?cache=0&sync_timestamp=1591162286391&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.58.tgz", + "integrity": "sha1-juIO7zD6YOUnVfzAlC3vWnNP6TU=", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.3", + "resolved": "https://registry.npm.taobao.org/@babel/preset-modules/download/@babel/preset-modules-0.1.3.tgz", + "integrity": "sha1-EyQrU7XvjIg8PPfd3VWzbOgPvHI=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/preset-react/download/@babel/preset-react-7.10.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpreset-react%2Fdownload%2F%40babel%2Fpreset-react-7.10.4.tgz", + "integrity": "sha1-kuimbYFvmRHRHUzJNb5nrfyC288=", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.10.4", + "@babel/plugin-transform-react-jsx": "^7.10.4", + "@babel/plugin-transform-react-jsx-development": "^7.10.4", + "@babel/plugin-transform-react-jsx-self": "^7.10.4", + "@babel/plugin-transform-react-jsx-source": "^7.10.4", + "@babel/plugin-transform-react-pure-annotations": "^7.10.4" + } + }, + "@babel/runtime": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/runtime/download/@babel/runtime-7.10.4.tgz?cache=0&sync_timestamp=1593521086699&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime%2Fdownload%2F%40babel%2Fruntime-7.10.4.tgz", + "integrity": "sha1-pnJPGmuNL26lI22/5Yx9fqnF65k=", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/runtime-corejs3/download/@babel/runtime-corejs3-7.10.4.tgz?cache=0&sync_timestamp=1593522791446&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fruntime-corejs3%2Fdownload%2F%40babel%2Fruntime-corejs3-7.10.4.tgz", + "integrity": "sha1-8p/BmQMHxMV7ENvWzmZ7JxWdng0=", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/template/download/@babel/template-7.10.4.tgz", + "integrity": "sha1-MlGZbEIA68cdGo/EBfupQPNrong=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz?cache=0&sync_timestamp=1593522826253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.10.4.tgz", + "integrity": "sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.10.4.tgz", + "integrity": "sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + } + } + }, + "@babel/traverse": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/traverse/download/@babel/traverse-7.10.4.tgz?cache=0&sync_timestamp=1593522841483&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftraverse%2Fdownload%2F%40babel%2Ftraverse-7.10.4.tgz", + "integrity": "sha1-5kLlOVo7CcyVyOdKJ0MrSEtpeBg=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/code-frame/download/@babel/code-frame-7.10.4.tgz?cache=0&sync_timestamp=1593522826253&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fcode-frame%2Fdownload%2F%40babel%2Fcode-frame-7.10.4.tgz", + "integrity": "sha1-Fo2ho26Q2miujUnA8bSMfGJJITo=", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.10.4.tgz", + "integrity": "sha1-fRvf1ldTU4+r5sOFls23bZrGAUM=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.10.4", + "resolved": "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.10.4.tgz?cache=0&sync_timestamp=1593521074992&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.10.4.tgz", + "integrity": "sha1-NpUXGINS4YIZmB79FWv9sZn/8e4=", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", + "dev": true + }, + "@types/d3-selection": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.0.10.tgz", + "integrity": "sha1-3PsN3837GtJq6kNRMjdx4a6pboQ=" + }, + "@types/glob": { + "version": "7.1.2", + "resolved": "https://registry.npm.taobao.org/@types/glob/download/@types/glob-7.1.2.tgz?cache=0&sync_timestamp=1591314114348&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fglob%2Fdownload%2F%40types%2Fglob-7.1.2.tgz", + "integrity": "sha1-BsomUhNTpUXZSgrcdPOKWdIyyYc=", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npm.taobao.org/@types/json-schema/download/@types/json-schema-7.0.5.tgz?cache=0&sync_timestamp=1591720889158&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fjson-schema%2Fdownload%2F%40types%2Fjson-schema-7.0.5.tgz", + "integrity": "sha1-3M5EMOZLRDuolF8CkPtWStW6xt0=", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npm.taobao.org/@types/json5/download/@types/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npm.taobao.org/@types/minimatch/download/@types/minimatch-3.0.3.tgz", + "integrity": "sha1-PcoOPzOyAPx9ETnAzZbBJoyt/Z0=", + "dev": true + }, + "@types/node": { + "version": "14.0.14", + "resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-14.0.14.tgz?cache=0&sync_timestamp=1592989417189&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.0.14.tgz", + "integrity": "sha1-JKC1lZ8WrBQa6wxbPNehW3xky84=", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/ast/download/@webassemblyjs/ast-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fast%2Fdownload%2F%40webassemblyjs%2Fast-1.9.0.tgz", + "integrity": "sha1-vYUGBLQEJFmlpBzX0zjL7Wle2WQ=", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/floating-point-hex-parser/download/@webassemblyjs/floating-point-hex-parser-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Ffloating-point-hex-parser%2Fdownload%2F%40webassemblyjs%2Ffloating-point-hex-parser-1.9.0.tgz", + "integrity": "sha1-PD07Jxvd/ITesA9xNEQ4MR1S/7Q=", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-api-error/download/@webassemblyjs/helper-api-error-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-api-error%2Fdownload%2F%40webassemblyjs%2Fhelper-api-error-1.9.0.tgz", + "integrity": "sha1-ID9nbjM7lsnaLuqzzO8zxFkotqI=", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-buffer/download/@webassemblyjs/helper-buffer-1.9.0.tgz?cache=0&sync_timestamp=1580600188490&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-buffer%2Fdownload%2F%40webassemblyjs%2Fhelper-buffer-1.9.0.tgz", + "integrity": "sha1-oUQtJpxf6yP8vJ73WdrDVH8p3gA=", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-code-frame/download/@webassemblyjs/helper-code-frame-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-code-frame%2Fdownload%2F%40webassemblyjs%2Fhelper-code-frame-1.9.0.tgz", + "integrity": "sha1-ZH+Iks0gQ6gqwMjF51w28dkVnyc=", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-fsm/download/@webassemblyjs/helper-fsm-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-fsm%2Fdownload%2F%40webassemblyjs%2Fhelper-fsm-1.9.0.tgz", + "integrity": "sha1-wFJWtxJEIUZx9LCOwQitY7cO3bg=", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-module-context/download/@webassemblyjs/helper-module-context-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-module-context%2Fdownload%2F%40webassemblyjs%2Fhelper-module-context-1.9.0.tgz", + "integrity": "sha1-JdiIS3aDmHGgimxvgGw5ee9xLwc=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-wasm-bytecode/download/@webassemblyjs/helper-wasm-bytecode-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-wasm-bytecode%2Fdownload%2F%40webassemblyjs%2Fhelper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha1-T+2L6sm4wU+MWLcNEk1UndH+V5A=", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/helper-wasm-section/download/@webassemblyjs/helper-wasm-section-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fhelper-wasm-section%2Fdownload%2F%40webassemblyjs%2Fhelper-wasm-section-1.9.0.tgz", + "integrity": "sha1-WkE41aYpK6GLBMWuSXF+QWeWU0Y=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/ieee754/download/@webassemblyjs/ieee754-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fieee754%2Fdownload%2F%40webassemblyjs%2Fieee754-1.9.0.tgz", + "integrity": "sha1-Fceg+6roP7JhQ7us9tbfFwKtOeQ=", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/leb128/download/@webassemblyjs/leb128-1.9.0.tgz", + "integrity": "sha1-8Zygt2ptxVYjoJz/p2noOPoeHJU=", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/utf8/download/@webassemblyjs/utf8-1.9.0.tgz", + "integrity": "sha1-BNM7Y2945qaBMifoJAL3Y3tiKas=", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wasm-edit/download/@webassemblyjs/wasm-edit-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwasm-edit%2Fdownload%2F%40webassemblyjs%2Fwasm-edit-1.9.0.tgz", + "integrity": "sha1-P+bXnT8PkiGDqoYALELdJWz+6c8=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wasm-gen/download/@webassemblyjs/wasm-gen-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwasm-gen%2Fdownload%2F%40webassemblyjs%2Fwasm-gen-1.9.0.tgz", + "integrity": "sha1-ULxw7Gje2OJ2OwGhQYv0NJGnpJw=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wasm-opt/download/@webassemblyjs/wasm-opt-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwasm-opt%2Fdownload%2F%40webassemblyjs%2Fwasm-opt-1.9.0.tgz", + "integrity": "sha1-IhEYHlsxMmRDzIES658LkChyGmE=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wasm-parser/download/@webassemblyjs/wasm-parser-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwasm-parser%2Fdownload%2F%40webassemblyjs%2Fwasm-parser-1.9.0.tgz", + "integrity": "sha1-nUjkSCbfSmWYKUqmyHRp1kL/9l4=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wast-parser/download/@webassemblyjs/wast-parser-1.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwast-parser%2Fdownload%2F%40webassemblyjs%2Fwast-parser-1.9.0.tgz", + "integrity": "sha1-MDERXXmsW9JhVWzsw/qQo+9FGRQ=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npm.taobao.org/@webassemblyjs/wast-printer/download/@webassemblyjs/wast-printer-1.9.0.tgz?cache=0&sync_timestamp=1580599638157&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40webassemblyjs%2Fwast-printer%2Fdownload%2F%40webassemblyjs%2Fwast-printer-1.9.0.tgz", + "integrity": "sha1-STXVTIX+9jewDOn1I3dFHQDUeJk=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/@xtuc/ieee754/download/@xtuc/ieee754-1.2.0.tgz", + "integrity": "sha1-7vAUoxRa5Hehy8AM0eVSM23Ot5A=", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npm.taobao.org/@xtuc/long/download/@xtuc/long-4.2.2.tgz", + "integrity": "sha1-0pHGpOl5ibXGHZrPOWrk/hM6cY0=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz", + "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", + "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npm.taobao.org/ansi-colors/download/ansi-colors-3.2.4.tgz", + "integrity": "sha1-46PaS/uubIapwoViXeEkojQCb78=", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npm.taobao.org/ansi-html/download/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-2.0.0.tgz", + "integrity": "sha1-vLJLTzeTTZqnrBe0ra+J58du8us=", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/normalize-path/download/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/aproba/download/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npm.taobao.org/aria-query/download/aria-query-4.2.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faria-query%2Fdownload%2Faria-query-4.2.2.tgz", + "integrity": "sha1-DSymyazrVriXfp/tau1+FbvS+Ds=", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/arr-diff/download/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/arr-flatten/download/arr-flatten-1.1.0.tgz", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/arr-union/download/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/array-flatten/download/array-flatten-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-flatten%2Fdownload%2Farray-flatten-2.1.2.tgz", + "integrity": "sha1-JO+AoowaiTYX4hSbDG0NeIKTsJk=", + "dev": true + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/array-includes/download/array-includes-3.1.1.tgz", + "integrity": "sha1-zdZ+aFK9+cEhVGB4ZzIlXtJFk0g=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/array-uniq/download/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npm.taobao.org/array-unique/download/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npm.taobao.org/array.prototype.flat/download/array.prototype.flat-1.2.3.tgz", + "integrity": "sha1-DegrQmsDGNv9uUAInjiwQ9N/bHs=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npm.taobao.org/array.prototype.flatmap/download/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha1-HBP4SheFZgQt1j3kQURA25Ii5EM=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npm.taobao.org/asn1/download/asn1-0.2.4.tgz", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npm.taobao.org/asn1.js/download/asn1.js-4.10.1.tgz", + "integrity": "sha1-ucK/WAXx5kqt7tbfOiv6+1pz9aA=", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npm.taobao.org/assert/download/assert-1.5.0.tgz", + "integrity": "sha1-VcEJqvbgrv2z3EtxJAxwv1dLGOs=", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npm.taobao.org/util/download/util-0.10.3.tgz?cache=0&sync_timestamp=1588238457176&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Futil%2Fdownload%2Futil-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "optional": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/assign-symbols/download/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npm.taobao.org/ast-types-flow/download/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npm.taobao.org/async/download/async-2.6.3.tgz", + "integrity": "sha1-1yYl4jRKNlbjo61Pp0n6gymdgv8=", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/async-each/download/async-each-1.0.3.tgz", + "integrity": "sha1-tyfb+H12UWAvBvTUrDh/R9kbDL8=", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/async-limiter/download/async-limiter-1.0.1.tgz?cache=0&sync_timestamp=1574272018408&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fasync-limiter%2Fdownload%2Fasync-limiter-1.0.1.tgz", + "integrity": "sha1-3TeelPDbgxCwgpH51kwyCXZmF/0=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true, + "optional": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/atob/download/atob-2.1.2.tgz", + "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "dev": true + }, + "autoprefixer": { + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.2.tgz", + "integrity": "sha512-LCAfcdej1182uVvPOZnytbq61AhnOZ/4JelDaJGDeNwewyU1AMaNthcHsyz1NRjTmd2FkurMckLWfkHg3Z//KA==", + "dev": true, + "requires": { + "browserslist": "^4.7.3", + "caniuse-lite": "^1.0.30001010", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.23", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npm.taobao.org/aws4/download/aws4-1.10.0.tgz", + "integrity": "sha1-oXs6jqgRBg501H0wYSJACtRJeuI=", + "dev": true, + "optional": true + }, + "axe-core": { + "version": "3.5.5", + "resolved": "https://registry.npm.taobao.org/axe-core/download/axe-core-3.5.5.tgz?cache=0&sync_timestamp=1593618499473&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faxe-core%2Fdownload%2Faxe-core-3.5.5.tgz", + "integrity": "sha1-hDFQc7U/o8DFFnbFiNWdoJoZIic=", + "dev": true + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/axobject-query/download/axobject-query-2.2.0.tgz?cache=0&sync_timestamp=1592785103417&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Faxobject-query%2Fdownload%2Faxobject-query-2.2.0.tgz", + "integrity": "sha1-lD1H4QwLcEqkInXiDt83ImSJib4=", + "dev": true + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npm.taobao.org/babel-eslint/download/babel-eslint-10.1.0.tgz", + "integrity": "sha1-aWjlaKkQt4+zd5zdi2rC9HmUMjI=", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npm.taobao.org/babel-loader/download/babel-loader-8.1.0.tgz?cache=0&sync_timestamp=1584715910722&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbabel-loader%2Fdownload%2Fbabel-loader-8.1.0.tgz", + "integrity": "sha1-xhHVESvVIJq+i5+oTD5NolJ18cM=", + "dev": true, + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npm.taobao.org/ajv/download/ajv-6.12.2.tgz?cache=0&sync_timestamp=1587338610933&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv%2Fdownload%2Fajv-6.12.2.tgz", + "integrity": "sha1-xinF7O0XuvMUQ3kY0tqIyZ1ZWM0=", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz", + "integrity": "sha1-VXBmIEatKeLpFucariYKvf9Pang=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-3.1.3.tgz?cache=0&sync_timestamp=1591599604098&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-3.1.3.tgz", + "integrity": "sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/json5/download/json5-1.0.1.tgz?cache=0&sync_timestamp=1586046271069&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-1.0.1.tgz", + "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-1.4.0.tgz", + "integrity": "sha1-xXm140yzSxp07cbB+za/o3HVphM=", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz?cache=0&sync_timestamp=1587535418745&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-0.5.5.tgz", + "integrity": "sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8=", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz", + "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=", + "dev": true + } + } + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npm.taobao.org/schema-utils/download/schema-utils-2.7.0.tgz", + "integrity": "sha1-FxUfdtjq5n+793lgwzxnatn078c=", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npm.taobao.org/babel-plugin-dynamic-import-node/download/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha1-hP2hnJduxcbe/vV/lCez3vZuF6M=", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-module-resolver": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-3.2.0.tgz", + "integrity": "sha512-tjR0GvSndzPew/Iayf4uICWZqjBwnlMWjSx6brryfQ81F9rxBVqwDJtFCV8oOs0+vJeefK9TmdZtkIFdFe1UnA==", + "dev": true, + "requires": { + "find-babel-config": "^1.1.0", + "glob": "^7.1.2", + "pkg-up": "^2.0.0", + "reselect": "^3.0.1", + "resolve": "^1.4.0" + } + }, + "babel-plugin-transform-inline-environment-variables": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-environment-variables/-/babel-plugin-transform-inline-environment-variables-0.4.3.tgz", + "integrity": "sha1-o7CYgzU76LXiM24/8e+KXZP5xIk=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npm.taobao.org/base/download/base-0.11.2.tgz", + "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npm.taobao.org/base64-js/download/base64-js-1.3.1.tgz", + "integrity": "sha1-WOzoy3XdB+ce0IxzarxfrE2/jfE=", + "dev": true + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/batch/download/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-1.13.1.tgz?cache=0&sync_timestamp=1593261331793&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-1.13.1.tgz", + "integrity": "sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npm.taobao.org/bindings/download/bindings-1.5.0.tgz", + "integrity": "sha1-EDU8npRTNLwFEabZCzj7x8nFBN8=", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.7.2.tgz", + "integrity": "sha1-nyKcFb4nJFT/qXOs4NvueaGww28=", + "dev": true + }, + "bn.js": { + "version": "5.1.2", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-5.1.2.tgz", + "integrity": "sha1-yWhpAtPJoncp9DqxD515wgBNp7A=", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz", + "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.1.0.tgz", + "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz", + "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npm.taobao.org/bonjour/download/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npm.taobao.org/braces/download/braces-2.3.2.tgz", + "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/brorand/download/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/browserify-aes/download/browserify-aes-1.2.0.tgz", + "integrity": "sha1-Mmc0ZC9APavDADIJhTu3CtQo70g=", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/browserify-cipher/download/browserify-cipher-1.0.1.tgz", + "integrity": "sha1-jWR0wbhwv9q807z8wZNKEOlPFfA=", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/browserify-des/download/browserify-des-1.0.2.tgz", + "integrity": "sha1-OvTx9Zg5QDVy8cZiBDdfen9wPpw=", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/browserify-rsa/download/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.2.0", + "resolved": "https://registry.npm.taobao.org/browserify-sign/download/browserify-sign-4.2.0.tgz", + "integrity": "sha1-VF0LGwfmssmSEQgr8bEsznoLDhE=", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.2", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz", + "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz", + "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npm.taobao.org/browserify-zlib/download/browserify-zlib-0.2.0.tgz", + "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.3.tgz", + "integrity": "sha512-jWvmhqYpx+9EZm/FxcZSbUZyDEvDTLDi3nSAKbzEkyWvtI0mNSmUosey+5awDW1RUlrgXbQb5A6qY1xQH9U6MQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001010", + "electron-to-chromium": "^1.3.306", + "node-releases": "^1.1.40" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npm.taobao.org/buffer/download/buffer-4.9.2.tgz?cache=0&sync_timestamp=1588706716358&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbuffer%2Fdownload%2Fbuffer-4.9.2.tgz", + "integrity": "sha1-Iw6tNEACmIZEhBqwJEr4xEu+Pvg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", + "dev": true + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/buffer-indexof/download/buffer-indexof-1.1.1.tgz", + "integrity": "sha1-Uvq8xqYG0aADAoAmSO9o9jnaJow=", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/buffer-xor/download/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npm.taobao.org/cacache/download/cacache-12.0.4.tgz?cache=0&sync_timestamp=1591142705598&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacache%2Fdownload%2Fcacache-12.0.4.tgz", + "integrity": "sha1-ZovL0QWutfHZL+JVcOyVJcj6pAw=", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/cache-base/download/cache-base-1.0.1.tgz", + "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001011", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001011.tgz", + "integrity": "sha512-h+Eqyn/YA6o6ZTqpS86PyRmNWOs1r54EBDcd2NTwwfsXQ8re1B38SnB+p2RKF8OUsyEIjeDU8XGec1RGO/wYCg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true, + "optional": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-2.1.8.tgz?cache=0&sync_timestamp=1587911196018&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-2.1.8.tgz", + "integrity": "sha1-gEs6e2qZNYw8XGHnHYco8EHP+Rc=", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npm.taobao.org/chownr/download/chownr-1.1.4.tgz", + "integrity": "sha1-b8nXtC0ypYNZYzdmbn0ICE2izGs=", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/chrome-trace-event/download/chrome-trace-event-1.0.2.tgz", + "integrity": "sha1-I0CQ7pfH1K0aLEvq4nUF3v/GCKQ=", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/cipher-base/download/cipher-base-1.0.4.tgz", + "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npm.taobao.org/class-utils/download/class-utils-0.3.6.tgz", + "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npm.taobao.org/cliui/download/cliui-5.0.0.tgz?cache=0&sync_timestamp=1573942320052&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcliui%2Fdownload%2Fcliui-5.0.0.tgz", + "integrity": "sha1-3u/P2y6AB4SqNPRvoI4GhRx7u8U=", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-4.1.0.tgz", + "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/string-width/download/string-width-3.1.0.tgz", + "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz", + "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "codemirror": { + "version": "5.55.0", + "resolved": "https://registry.npm.taobao.org/codemirror/download/codemirror-5.55.0.tgz?cache=0&sync_timestamp=1592745428423&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcodemirror%2Fdownload%2Fcodemirror-5.55.0.tgz", + "integrity": "sha1-I3MfZBKI8gKmhY/ch48xSeDgQ2M=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/collection-visit/download/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz", + "integrity": "sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/commondir/download/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/component-emitter/download/component-emitter-1.3.0.tgz", + "integrity": "sha1-FuQHD7qK4ptnnyIVhT7hgasuq8A=", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npm.taobao.org/compressible/download/compressible-2.0.18.tgz?cache=0&sync_timestamp=1578286264482&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompressible%2Fdownload%2Fcompressible-2.0.18.tgz", + "integrity": "sha1-r1PMprBw1MPAdQ+9dyhqbXzEb7o=", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npm.taobao.org/compression/download/compression-1.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompression%2Fdownload%2Fcompression-1.7.4.tgz", + "integrity": "sha1-lVI+/xcMpXwpoMpB5v4TH0Hlu48=", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz", + "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npm.taobao.org/connect-history-api-fallback/download/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha1-izIIk1kwjRERFdgcrT/Oq4iPl7w=", + "dev": true + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/console-browserify/download/console-browserify-1.2.0.tgz", + "integrity": "sha1-ZwY871fOts9Jk6KrOlWECujEkzY=", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/constants-browserify/download/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npm.taobao.org/contains-path/download/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcontent-disposition%2Fdownload%2Fcontent-disposition-0.5.3.tgz", + "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "dev": true + }, + "conventional-commit-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-2.3.0.tgz", + "integrity": "sha512-6iB39PrcGYdz0n3z31kj6/Km6mK9hm9oMRhwcLnKxE7WNoeRKZbTAobliKrbYZ5jqyCvtcVEfjCiaEzhL3AVmQ==", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npm.taobao.org/convert-source-map/download/convert-source-map-1.7.0.tgz", + "integrity": "sha1-F6LLiC1/d9NJBYXizmxSRCSjpEI=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz", + "integrity": "sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo=", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npm.taobao.org/copy-concurrently/download/copy-concurrently-1.0.5.tgz", + "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/copy-descriptor/download/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npm.taobao.org/core-js/download/core-js-3.6.5.tgz?cache=0&sync_timestamp=1586450269267&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js%2Fdownload%2Fcore-js-3.6.5.tgz", + "integrity": "sha1-c5XcJzrzf7LlDpvT2f6EEoUjHRo=" + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npm.taobao.org/core-js-compat/download/core-js-compat-3.6.5.tgz?cache=0&sync_timestamp=1586535809290&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcore-js-compat%2Fdownload%2Fcore-js-compat-3.6.5.tgz", + "integrity": "sha1-KlHZpOJd/W5pAlGqgfmePAVIHxw=", + "dev": true, + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.12.2", + "resolved": "https://registry.npm.taobao.org/browserslist/download/browserslist-4.12.2.tgz?cache=0&sync_timestamp=1593190408386&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.12.2.tgz", + "integrity": "sha1-dmU9fkxXyqihooUT4vThl9wRpxE=", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001088", + "electron-to-chromium": "^1.3.483", + "escalade": "^3.0.1", + "node-releases": "^1.1.58" + } + }, + "caniuse-lite": { + "version": "1.0.30001093", + "resolved": "https://registry.npm.taobao.org/caniuse-lite/download/caniuse-lite-1.0.30001093.tgz?cache=0&sync_timestamp=1593641273200&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001093.tgz", + "integrity": "sha1-gz6A9ksaBFXLzu0qSjuvGeSr0xI=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.484", + "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.484.tgz?cache=0&sync_timestamp=1593660932805&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.484.tgz", + "integrity": "sha1-dfWh7uX+MWh1i3ws83Wuc8HM9eY=", + "dev": true + }, + "node-releases": { + "version": "1.1.58", + "resolved": "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.58.tgz?cache=0&sync_timestamp=1591162286391&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.58.tgz", + "integrity": "sha1-juIO7zD6YOUnVfzAlC3vWnNP6TU=", + "dev": true + }, + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npm.taobao.org/semver/download/semver-7.0.0.tgz", + "integrity": "sha1-XzyjV2HkfgWyBsba/yz4FPAxa44=", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npm.taobao.org/core-js-pure/download/core-js-pure-3.6.5.tgz", + "integrity": "sha1-x5519eONvIWmYtke6lK4JW1TuBM=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "dependencies": { + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npm.taobao.org/create-ecdh/download/create-ecdh-4.0.3.tgz", + "integrity": "sha1-yREbbzMEXEaX8UR4f5JUzcd8Rf8=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/create-hash/download/create-hash-1.2.0.tgz", + "integrity": "sha1-iJB4rxGmN1a8+1m9IhmWvjqe8ZY=", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npm.taobao.org/create-hmac/download/create-hmac-1.1.7.tgz", + "integrity": "sha1-aRcMeLOrlXFHsriwRXLkfq0iQ/8=", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "create-react-class": { + "version": "15.6.3", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", + "requires": { + "fbjs": "^0.8.9", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "cross-env": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", + "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.5" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npm.taobao.org/crypto-browserify/download/crypto-browserify-3.12.0.tgz", + "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "css-loader": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz", + "integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==", + "dev": true, + "requires": { + "camelcase": "^5.2.0", + "icss-utils": "^4.1.0", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.14", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^2.0.6", + "postcss-modules-scope": "^2.1.0", + "postcss-modules-values": "^2.0.0", + "postcss-value-parser": "^3.3.0", + "schema-utils": "^1.0.0" + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "dev": true + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/cyclist/download/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "cz-conventional-changelog": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz", + "integrity": "sha1-L0vHOQ4yROTfKT5ro1Hkx0Cnx2Q=", + "dev": true, + "requires": { + "conventional-commit-types": "^2.0.0", + "lodash.map": "^4.5.1", + "longest": "^1.0.1", + "right-pad": "^1.0.1", + "word-wrap": "^1.0.3" + } + }, + "d3": { + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.14.2.tgz", + "integrity": "sha512-Ccipa9XrYW5N0QkP6u0Qb8kU6WekIXBiDenmZm1zLvuq/9pBBhRCJLCICEOsH5Og4B0Xw02bhqGkK5VN/oPH0w==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", + "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", + "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" + }, + "d3-fetch": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", + "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-gco1Ih54PgMsyIXgttLxEhNy/mXxq8+rLnCb5shQk+P5TsiySrwWU5gpB4zen626J4LIwBxHvDChyA8qDm57ww==" + }, + "d3-geo": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.9.tgz", + "integrity": "sha512-9edcH6J3s/Aa3KJITWqFJbyB/8q3mMlA9Fi7z6yy+FAYMnRaxmC7jBhUnsINxVWD14GmqX3DK8uk7nV6/Ekt4A==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "d3-interpolate": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.6.tgz", + "integrity": "sha512-mOnv5a+pZzkNIHtw/V6I+w9Lqm9L5bG3OTXPM5A+QO0yyVMQ4W1uZhR+VOJmazaOZXri2ppbiZ5BUNWT0pFM9A==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-sankey": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.7.1.tgz", + "integrity": "sha1-0imDImj8aaf+yEgD6WwiVqYUxSE=", + "requires": { + "d3-array": "1", + "d3-collection": "1", + "d3-shape": "^1.2.0" + } + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz", + "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-svg-legend": { + "version": "2.25.6", + "resolved": "https://registry.npmjs.org/d3-svg-legend/-/d3-svg-legend-2.25.6.tgz", + "integrity": "sha1-jY3BvWk8N47ki2+CPook5o8uGtI=", + "requires": { + "@types/d3-selection": "1.0.10", + "d3-array": "1.0.1", + "d3-dispatch": "1.0.1", + "d3-format": "1.0.2", + "d3-scale": "1.0.3", + "d3-selection": "1.0.2", + "d3-transition": "1.0.3" + }, + "dependencies": { + "d3-array": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.0.1.tgz", + "integrity": "sha1-N1wCh0/NlsFu2fG89bSnvlPzWOc=" + }, + "d3-dispatch": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.1.tgz", + "integrity": "sha1-S9ZaQ87P9DGN653yRVKqi/KBqEA=" + }, + "d3-format": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.0.2.tgz", + "integrity": "sha1-E4YYMgtLvrQ7XA/zBRkHn7vXN14=" + }, + "d3-scale": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.3.tgz", + "integrity": "sha1-T56PDMLqDzkl/wSsJ63AkEX6TJA=", + "requires": { + "d3-array": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-selection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.0.2.tgz", + "integrity": "sha1-rmYq/UcCrJxdoDmyEHoXZPockHA=" + }, + "d3-transition": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.0.3.tgz", + "integrity": "sha1-kdyYa92zCXNjkyCoXbcs5KsaJ7s=", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-timer": "1" + } + } + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.2.tgz", + "integrity": "sha512-pweL2Ri2wqMY+wlW/wpkl8T3CUzKAha8S9nmiQlMABab8r5MJN0PD1V4YyRNVaKQfeh4Z0+VO70TLw6ESVOYzw==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "dagre": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.4.tgz", + "integrity": "sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==", + "requires": { + "graphlib": "^2.1.7", + "lodash": "^4.17.4" + } + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npm.taobao.org/damerau-levenshtein/download/damerau-levenshtein-1.0.6.tgz?cache=0&sync_timestamp=1580133385754&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdamerau-levenshtein%2Fdownload%2Fdamerau-levenshtein-1.0.6.tgz", + "integrity": "sha1-FDwWQcs9hcYMMjKeJoma3qhwF5E=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1580010393599&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npm.taobao.org/decode-uri-component/download/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/deep-equal/download/deep-equal-1.1.1.tgz", + "integrity": "sha1-tcmMlCzv+vfLBR4k4UNKJaLmB2o=", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npm.taobao.org/default-gateway/download/default-gateway-4.2.0.tgz?cache=0&sync_timestamp=1590419212936&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdefault-gateway%2Fdownload%2Fdefault-gateway-4.2.0.tgz", + "integrity": "sha1-FnEEx1AMIRX23WmwpTa7jtcgVSs=", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-2.0.2.tgz", + "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/del/download/del-4.1.1.tgz", + "integrity": "sha1-no8RciLqRKMf86FWwEm5kFKp8LQ=", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "optional": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/des.js/download/des.js-1.0.1.tgz", + "integrity": "sha1-U4IULhvcU/hdhtU+X0qn3rkeCEM=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/detect-file/download/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz", + "integrity": "sha1-AU7o+PZpxcWAI9pkuBecCDooxGw=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npm.taobao.org/diffie-hellman/download/diffie-hellman-5.0.3.tgz", + "integrity": "sha1-QOjumPVaIUlgcUaSHGPhrl89KHU=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npm.taobao.org/dns-packet/download/dns-packet-1.3.1.tgz", + "integrity": "sha1-EqpCaYEHW+UAuRDu3NC0fdfe2lo=", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/dns-txt/download/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-to-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz", + "integrity": "sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc=" + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz", + "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npm.taobao.org/duplexify/download/duplexify-3.7.1.tgz", + "integrity": "sha1-Kk31MX9sz9kfhtb9JdjYoQO4gwk=", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.309", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.309.tgz", + "integrity": "sha512-NZd91XD15v2UPLjYXoN/gLnkwIUQjdH4SQLpRCCQiYJH6BBkfgp5pWemBJPr1rZ2dl8Ee3o91O9Sa1QuAfZmog==", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npm.taobao.org/elliptic/download/elliptic-6.5.3.tgz?cache=0&sync_timestamp=1592492805287&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felliptic%2Fdownload%2Felliptic-6.5.3.tgz", + "integrity": "sha1-y1nrLv2vc6C9eMzXAVpirW4Pk9Y=", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.4.tgz", + "integrity": "sha1-WuZKX0UFe682JuwU2gyl5LJDHrA=", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npm.taobao.org/enhanced-resolve/download/enhanced-resolve-4.2.0.tgz?cache=0&sync_timestamp=1592387355340&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fenhanced-resolve%2Fdownload%2Fenhanced-resolve-4.2.0.tgz", + "integrity": "sha1-XUO9pKD9RHyw675xvvje/4gFrQ0=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.5.0.tgz?cache=0&sync_timestamp=1570537491040&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmemory-fs%2Fdownload%2Fmemory-fs-0.5.0.tgz", + "integrity": "sha1-MkwBKIuIZSlm0WHbd4OHIIRajjw=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npm.taobao.org/errno/download/errno-0.1.7.tgz", + "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/escalade/download/escalade-3.0.1.tgz", + "integrity": "sha1-UlaKd0Q/aSfNCrnHMSkTdTPJZe0=", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "eslint-config-airbnb": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.1.tgz", + "integrity": "sha512-xCu//8a/aWqagKljt+1/qAM62BYZeNq04HmdevG5yUGWpja0I/xhqd6GdLRch5oetEGFiJAnvtGuTEAese53Qg==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^13.2.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-base": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz", + "integrity": "sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.5", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npm.taobao.org/eslint-import-resolver-node/download/eslint-import-resolver-node-0.3.4.tgz?cache=0&sync_timestamp=1592327223893&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-import-resolver-node%2Fdownload%2Feslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha1-hf+oGULCUBLYIxCW3fZ5wDBCxxc=", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.17.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.17.0.tgz", + "integrity": "sha1-sllBtUloIxzC0bt2p5y38sC/hEQ=", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npm.taobao.org/eslint-module-utils/download/eslint-module-utils-2.6.0.tgz", + "integrity": "sha1-V569CU9Wr3eX0ZyYZsnJSGYpv6Y=", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/p-limit/download/p-limit-1.3.0.tgz", + "integrity": "sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg=", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.22.0", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.22.0.tgz?cache=0&sync_timestamp=1593237431320&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-import%2Fdownload%2Feslint-plugin-import-2.22.0.tgz", + "integrity": "sha1-kvdzb+H94+Led2I8g43Zkv9f+34=", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npm.taobao.org/doctrine/download/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.17.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.17.0.tgz", + "integrity": "sha1-sllBtUloIxzC0bt2p5y38sC/hEQ=", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.3.1", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-jsx-a11y/download/eslint-plugin-jsx-a11y-6.3.1.tgz?cache=0&sync_timestamp=1592629539885&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-jsx-a11y%2Fdownload%2Feslint-plugin-jsx-a11y-6.3.1.tgz", + "integrity": "sha1-me9+l/VnzGpbjdWrlalKZwWKJmA=", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^3.5.4", + "axobject-query": "^2.1.2", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1", + "language-tags": "^1.0.5" + }, + "dependencies": { + "emoji-regex": { + "version": "9.0.0", + "resolved": "https://registry.npm.taobao.org/emoji-regex/download/emoji-regex-9.0.0.tgz", + "integrity": "sha1-SKIwnMih0unSO8amfDm2MDLnbqQ=", + "dev": true + } + } + }, + "eslint-plugin-react": { + "version": "7.20.3", + "resolved": "https://registry.npm.taobao.org/eslint-plugin-react/download/eslint-plugin-react-7.20.3.tgz?cache=0&sync_timestamp=1593549287847&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feslint-plugin-react%2Fdownload%2Feslint-plugin-react-7.20.3.tgz", + "integrity": "sha1-BZBSXn64OJDOcfc8LPg2KErYwvE=", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.17.0", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/doctrine/download/doctrine-2.1.0.tgz", + "integrity": "sha1-XNAfwQFiG0LEzX9dGmYkNxbT850=", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/object.entries/download/object.entries-1.1.2.tgz", + "integrity": "sha1-vHPwCstra7FsIDQ0sQ+afnl9Ot0=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npm.taobao.org/resolve/download/resolve-1.17.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.17.0.tgz", + "integrity": "sha1-sllBtUloIxzC0bt2p5y38sC/hEQ=", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-4.0.4.tgz?cache=0&sync_timestamp=1589283150629&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feventemitter3%2Fdownload%2Feventemitter3-4.0.4.tgz", + "integrity": "sha1-tUY6zmNaCD0Bi9x8kXtMXxCoU4Q=", + "dev": true + }, + "events": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/events/download/events-3.1.0.tgz?cache=0&sync_timestamp=1578498298945&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fevents%2Fdownload%2Fevents-3.1.0.tgz", + "integrity": "sha1-hCea8bNMt1qoi/X/KR9tC9mzGlk=", + "dev": true + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npm.taobao.org/eventsource/download/eventsource-1.0.7.tgz", + "integrity": "sha1-j7xyyT/NNAiAkLwKTmT0tc7m2NA=", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/evp_bytestokey/download/evp_bytestokey-1.0.3.tgz", + "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/execa/download/execa-1.0.0.tgz", + "integrity": "sha1-xiNqW7TfbW8V6I5/AXeYIWdJ3dg=", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npm.taobao.org/expand-brackets/download/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/expand-tilde/download/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexpress%2Fdownload%2Fexpress-4.17.1.tgz", + "integrity": "sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ=", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-flatten%2Fdownload%2Farray-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz", + "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=", + "dev": true, + "optional": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/extglob/download/extglob-2.0.4.tgz", + "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "optional": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npm.taobao.org/faye-websocket/download/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fbjs": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", + "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npm.taobao.org/figgy-pudding/download/figgy-pudding-3.5.2.tgz", + "integrity": "sha1-tO7oFIq7Adzx0aw0Nn1Z4S+mHW4=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/file-uri-to-path/download/file-uri-to-path-1.0.0.tgz", + "integrity": "sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz", + "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-babel-config": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.0.tgz", + "integrity": "sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA==", + "dev": true, + "requires": { + "json5": "^0.5.1", + "path-exists": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + } + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/find-cache-dir/download/find-cache-dir-2.1.0.tgz?cache=0&sync_timestamp=1583734591888&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-cache-dir%2Fdownload%2Ffind-cache-dir-2.1.0.tgz", + "integrity": "sha1-jQ+UzRP+Q8bHwmGg2GEVypGMBfc=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-3.0.0.tgz", + "integrity": "sha1-SRafHXmTQwZG2mHsxa41XCHJe3M=", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/findup-sync/download/findup-sync-3.0.0.tgz", + "integrity": "sha1-F7EI+e5RLft6XH88iyfqnhqcCNE=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/flush-write-stream/download/flush-write-stream-1.1.1.tgz", + "integrity": "sha1-jdfYc6G6vCB9lOrQwuDkQnbr8ug=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.12.1", + "resolved": "https://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.12.1.tgz?cache=0&sync_timestamp=1592518281721&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.12.1.tgz", + "integrity": "sha1-3lSmIFMRuT1gOY68Ac9wFWgjErY=", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/for-in/download/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz?cache=0&sync_timestamp=1573027118125&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fform-data%2Fdownload%2Fform-data-2.3.3.tgz", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", + "dev": true, + "optional": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npm.taobao.org/fragment-cache/download/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/from2/download/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/fs-readdir-recursive/download/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", + "dev": true + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npm.taobao.org/fs-write-stream-atomic/download/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-1.2.13.tgz?cache=0&sync_timestamp=1588787369955&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-1.2.13.tgz", + "integrity": "sha1-8yXLBFVZJCi88Rs4M3DvcOO/zDg=", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npm.taobao.org/gensync/download/gensync-1.0.0-beta.1.tgz", + "integrity": "sha1-WPQ2H/mH5f9uHnohCCeqNx6qwmk=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npm.taobao.org/get-caller-file/download/get-caller-file-2.0.5.tgz", + "integrity": "sha1-T5RBKoLbMvNuOwuXQfipf+sDH34=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/get-stream/download/get-stream-4.1.0.tgz", + "integrity": "sha1-wbJVV189wh1Zv8ec09K0axw6VLU=", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npm.taobao.org/get-value/download/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/glob-parent/download/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/is-glob/download/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/global-modules/download/global-modules-2.0.0.tgz", + "integrity": "sha1-mXYFrSNF8n9RU5vqJldEISFcd4A=", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/global-prefix/download/global-prefix-3.0.0.tgz", + "integrity": "sha1-/IX3MGTfafUEIfR/iD/luRO6m5c=", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/global-prefix/download/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/globby/download/globby-6.1.0.tgz?cache=0&sync_timestamp=1591083812416&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fglobby%2Fdownload%2Fglobby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.4.tgz?cache=0&sync_timestamp=1588086876757&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgraceful-fs%2Fdownload%2Fgraceful-fs-4.2.4.tgz", + "integrity": "sha1-Ila94U02MpWMRl68ltxGfKB6Kfs=", + "dev": true + }, + "graphlib": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", + "integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", + "requires": { + "lodash": "^4.17.5" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/handle-thing/download/handle-thing-2.0.1.tgz", + "integrity": "sha1-hX95zjWVgMNA1DCBzGSJcNC7I04=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "optional": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npm.taobao.org/har-validator/download/har-validator-5.1.3.tgz", + "integrity": "sha1-HvievT5JllV2de7ZiTEQ3DUPoIA=", + "dev": true, + "optional": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/has-value/download/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/has-values/download/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/hash-base/download/hash-base-3.1.0.tgz", + "integrity": "sha1-VcOB2eBuHSmXqIO0o/3f5/DTrzM=", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz", + "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.1.tgz", + "integrity": "sha1-Hq+fqb2x/dTsdfWPnNtOa3gn7sY=", + "dev": true + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npm.taobao.org/hash.js/download/hash.js-1.1.7.tgz", + "integrity": "sha1-C6vKU46NTuSg+JiNaIZlN6ADz0I=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/hmac-drbg/download/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/homedir-polyfill/download/homedir-polyfill-1.0.3.tgz", + "integrity": "sha1-dDKYzvTlrz4ZQWH7rcwhUdOgWOg=", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.8.8.tgz?cache=0&sync_timestamp=1583017392137&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhosted-git-info%2Fdownload%2Fhosted-git-info-2.8.8.tgz", + "integrity": "sha1-dTm9S8Hg4KiVgVouAmJCCxKFhIg=", + "dev": true + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npm.taobao.org/hpack.js/download/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npm.taobao.org/html-entities/download/html-entities-1.3.1.tgz", + "integrity": "sha1-+5oaS1sUxdq6gtPjTGrk/nAaDkQ=", + "dev": true + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npm.taobao.org/http-deceiver/download/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz?cache=0&sync_timestamp=1593407647372&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.7.2.tgz", + "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npm.taobao.org/http-proxy/download/http-proxy-1.18.1.tgz", + "integrity": "sha1-QBVB8FNIhLv5UmAzTnL4juOXZUk=", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npm.taobao.org/http-proxy-middleware/download/http-proxy-middleware-0.19.1.tgz?cache=0&sync_timestamp=1589915518285&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-proxy-middleware%2Fdownload%2Fhttp-proxy-middleware-0.19.1.tgz", + "integrity": "sha1-GDx9xKoUeRUDBkmMIQza+WCApDo=", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/https-browserify/download/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npm.taobao.org/ieee754/download/ieee754-1.1.13.tgz", + "integrity": "sha1-7BaFWOlaoYH9h9N/VcMrvLZwi4Q=", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npm.taobao.org/iferr/download/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/import-local/download/import-local-2.0.0.tgz", + "integrity": "sha1-VQcL44pZk88Y72236WH1vuXFoJ0=", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/infer-owner/download/infer-owner-1.0.4.tgz", + "integrity": "sha1-xM78qo5RBRwqQLos6KPScpWvlGc=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npm.taobao.org/internal-ip/download/internal-ip-4.3.0.tgz", + "integrity": "sha1-hFRSuq2dLKO2nGNaE3rLmg2tCQc=", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/internal-slot/download/internal-slot-1.0.2.tgz", + "integrity": "sha1-nC6fs82OXkJWxvRf4xAGf8+jeKM=", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/interpret/download/interpret-1.4.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finterpret%2Fdownload%2Finterpret-1.4.0.tgz", + "integrity": "sha1-Zlq4vE2iendKQFhOgS4+D6RbGh4=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npm.taobao.org/invariant/download/invariant-2.2.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finvariant%2Fdownload%2Finvariant-2.2.4.tgz", + "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/ip-regex/download/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz", + "integrity": "sha1-v/OFQ+64mEglB5/zoqjmy9RngbM=", + "dev": true + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npm.taobao.org/is-absolute-url/download/is-absolute-url-3.0.3.tgz?cache=0&sync_timestamp=1569736493122&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-absolute-url%2Fdownload%2Fis-absolute-url-3.0.3.tgz", + "integrity": "sha1-lsaiK2ojkpsR6gr7GDbDatSl1pg=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/is-arguments/download/is-arguments-1.0.4.tgz", + "integrity": "sha1-P6+WbHy6D/Q3+zH2JQCC/PBEjPM=", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-0.1.6.tgz", + "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz", + "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "dev": true + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/is-extendable/download/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz", + "integrity": "sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/is-number/download/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/is-path-cwd/download/is-path-cwd-2.2.0.tgz?cache=0&sync_timestamp=1562347283002&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-path-cwd%2Fdownload%2Fis-path-cwd-2.2.0.tgz", + "integrity": "sha1-Z9Q7gmZKe1GR/ZEZEn6zAASKn9s=", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-path-in-cwd/download/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha1-v+Lcomxp85cmWkAJljYCk1oFOss=", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-path-inside/download/is-path-inside-2.1.0.tgz", + "integrity": "sha1-fJgQWH1lmkDSe8201WFuqwWUlLI=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/is-plain-object/download/is-plain-object-2.0.4.tgz?cache=0&sync_timestamp=1593243689869&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-plain-object%2Fdownload%2Fis-plain-object-2.0.4.tgz", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npm.taobao.org/is-string/download/is-string-1.0.5.tgz", + "integrity": "sha1-QEk+0ZjvP/R3uMf5L2ROyCpc06Y=", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true, + "optional": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/is-windows/download/is-windows-1.0.2.tgz", + "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-wsl/download/is-wsl-1.1.0.tgz?cache=0&sync_timestamp=1588494180082&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-wsl%2Fdownload%2Fis-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/isobject/download/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npm.taobao.org/jsesc/download/jsesc-2.5.2.tgz", + "integrity": "sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true, + "optional": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true, + "optional": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npm.taobao.org/json3/download/json3-3.3.3.tgz", + "integrity": "sha1-f8EON1/FrkLEcFpcwKpvYr4wW4E=", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npm.taobao.org/json5/download/json5-2.1.3.tgz?cache=0&sync_timestamp=1586046271069&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-2.1.3.tgz", + "integrity": "sha1-ybD3+pIzv+WAf+ZvzzpWF+1ZfUM=", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz", + "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=", + "dev": true + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npm.taobao.org/jsx-ast-utils/download/jsx-ast-utils-2.4.1.tgz?cache=0&sync_timestamp=1591928158443&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjsx-ast-utils%2Fdownload%2Fjsx-ast-utils-2.4.1.tgz", + "integrity": "sha1-ERSkwSCUgdsGxpDCtPSIzGZfZX4=", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/killable/download/killable-1.0.1.tgz", + "integrity": "sha1-TIzkQRh6Bhx0dPuHygjipjgZSJI=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-6.0.3.tgz", + "integrity": "sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0=", + "dev": true + }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npm.taobao.org/language-subtag-registry/download/language-subtag-registry-0.3.20.tgz", + "integrity": "sha1-oAo3EhiU8iT3YyaOQxxVVWsMB1U=", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npm.taobao.org/language-tags/download/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "less": { + "version": "3.11.3", + "resolved": "https://registry.npm.taobao.org/less/download/less-3.11.3.tgz", + "integrity": "sha1-LYU5VPz+AWmor4aWILyqFlY9zBw=", + "dev": true, + "requires": { + "clone": "^2.1.2", + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "promise": "^7.1.1", + "request": "^2.83.0", + "source-map": "~0.6.0", + "tslib": "^1.10.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true, + "optional": true + } + } + }, + "less-loader": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", + "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "loader-utils": "^1.1.0", + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/leven/download/leven-3.1.0.tgz", + "integrity": "sha1-d4kd6DQGTMy6gq54QrtrFKE+1/I=", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/levenary/download/levenary-1.1.1.tgz", + "integrity": "sha1-hCqe6Y0gdap/ru2+MmeekgX0b3c=", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/load-json-file/download/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npm.taobao.org/loader-runner/download/loader-runner-2.4.0.tgz", + "integrity": "sha1-7UcGa/5TTX6ExMe5mYwqdWB9k1c=", + "dev": true + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-3.0.0.tgz", + "integrity": "sha1-2+w7OrdZdYBxtY/ln8QYca8hQA4=", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "loglevel": { + "version": "1.6.8", + "resolved": "https://registry.npm.taobao.org/loglevel/download/loglevel-1.6.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Floglevel%2Fdownload%2Floglevel-1.6.8.tgz", + "integrity": "sha1-iiX7ddCSIw7NRFcnDYC1TigBEXE=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npm.taobao.org/lru-cache/download/lru-cache-5.1.1.tgz", + "integrity": "sha1-HaJ+ZxAnGUdpXa9oSOhH8B2EuSA=", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/make-dir/download/make-dir-2.1.0.tgz?cache=0&sync_timestamp=1587567610342&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmake-dir%2Fdownload%2Fmake-dir-2.1.0.tgz", + "integrity": "sha1-XwMQ4YuL6JjMBwCSlaMK5B6R5vU=", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npm.taobao.org/map-cache/download/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/map-visit/download/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npm.taobao.org/md5.js/download/md5.js-1.3.5.tgz", + "integrity": "sha1-tdB7jjIW4+J81yjXL3DR5qNCAF8=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npm.taobao.org/memory-fs/download/memory-fs-0.4.1.tgz?cache=0&sync_timestamp=1570537491040&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmemory-fs%2Fdownload%2Fmemory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npm.taobao.org/micromatch/download/micromatch-3.1.10.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmicromatch%2Fdownload%2Fmicromatch-3.1.10.tgz", + "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/miller-rabin/download/miller-rabin-4.0.1.tgz", + "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz?cache=0&sync_timestamp=1590596706367&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-1.6.0.tgz", + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.44.0.tgz?cache=0&sync_timestamp=1587603398892&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.44.0.tgz", + "integrity": "sha1-+hHF6wrKEzS0Izy01S8QxaYnL5I=", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.27.tgz?cache=0&sync_timestamp=1587700357177&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.27.tgz", + "integrity": "sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8=", + "dev": true, + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/minimalistic-assert/download/minimalistic-assert-1.0.1.tgz", + "integrity": "sha1-LhlN4ERibUoQ5/f7wAznPoPk1cc=", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/minimalistic-crypto-utils/download/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/mississippi/download/mississippi-3.0.0.tgz", + "integrity": "sha1-6goykfl+C16HdrNj1fChLZTGcCI=", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npm.taobao.org/mixin-deep/download/mixin-deep-1.3.2.tgz", + "integrity": "sha1-ESC0PcNZp4Xc5ltVuC4lfM9HlWY=", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/is-extendable/download/is-extendable-1.0.1.tgz", + "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/move-concurrently/download/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npm.taobao.org/multicast-dns/download/multicast-dns-6.2.3.tgz?cache=0&sync_timestamp=1585239065356&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmulticast-dns%2Fdownload%2Fmulticast-dns-6.2.3.tgz", + "integrity": "sha1-oOx72QVcQoL3kMPIL04o2zsxsik=", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/multicast-dns-service-types/download/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npm.taobao.org/nan/download/nan-2.14.1.tgz", + "integrity": "sha1-174036MQW5FJTDFHCJMV7/iHSwE=", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz", + "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz", + "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npm.taobao.org/neo-async/download/neo-async-2.6.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fneo-async%2Fdownload%2Fneo-async-2.6.1.tgz", + "integrity": "sha1-rCetpmFn+ohJpq3dg39rGJrSCBw=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node-forge": { + "version": "0.9.0", + "resolved": "https://registry.npm.taobao.org/node-forge/download/node-forge-0.9.0.tgz?cache=0&sync_timestamp=1569524669712&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-forge%2Fdownload%2Fnode-forge-0.9.0.tgz", + "integrity": "sha1-1iQFDtu0SHStyhK7mlLsY8t4JXk=", + "dev": true + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/node-libs-browser/download/node-libs-browser-2.2.1.tgz", + "integrity": "sha1-tk9RPRgzhiX5A0bSew0jXmMfZCU=", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "node-releases": { + "version": "1.1.40", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.40.tgz", + "integrity": "sha512-r4LPcC5b/bS8BdtWH1fbeK88ib/wg9aqmg6/s3ngNLn2Ewkn/8J6Iw3P9RTlfIAdSdvYvQl2thCY5Y+qTAQ2iQ==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz", + "integrity": "sha1-5m2xg4sgDB38IzIl0SyzZSDiNKg=", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=", + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npm.taobao.org/object-copy/download/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/object-is/download/object-is-1.1.2.tgz?cache=0&sync_timestamp=1586894009620&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fobject-is%2Fdownload%2Fobject-is-1.1.2.tgz", + "integrity": "sha1-xdLof/nhGfeLegiEQVGeLuwVc7Y=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/object-visit/download/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", + "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/object.fromentries/download/object.fromentries-2.0.2.tgz", + "integrity": "sha1-SgnJubs4Q90PiazbUXp5TU81Wsk=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/object.pick/download/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/object.values/download/object.values-1.1.1.tgz", + "integrity": "sha1-aKmezeNWt+kpWjxeDOMdyMlT3l4=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/obuf/download/obuf-1.1.2.tgz", + "integrity": "sha1-Cb6jND1BhZ69RGKS0RydTbYZCE4=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/on-headers/download/on-headers-1.0.2.tgz", + "integrity": "sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npm.taobao.org/opn/download/opn-5.5.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fopn%2Fdownload%2Fopn-5.5.0.tgz", + "integrity": "sha1-/HFk+rVtI1kExRw7J9pnWMo7m/w=", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/original/download/original-1.0.2.tgz", + "integrity": "sha1-5EKmHP/hxf0gpl8yYcJmY7MD8l8=", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npm.taobao.org/os-browserify/download/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/p-limit/download/p-limit-2.3.0.tgz", + "integrity": "sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE=", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-3.0.0.tgz", + "integrity": "sha1-Mi1poFwCZLJZl9n0DNiokasAZKQ=", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/p-map/download/p-map-2.1.0.tgz", + "integrity": "sha1-MQko/u+cnsxltosXaTAYpmXOoXU=", + "dev": true + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/p-retry/download/p-retry-3.0.1.tgz", + "integrity": "sha1-MWtMiJPiyNwc+okfQGxLQivr8yg=", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-2.2.0.tgz", + "integrity": "sha1-yyhoVA4xPWHeWPr741zpAE1VQOY=", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npm.taobao.org/pako/download/pako-1.0.11.tgz", + "integrity": "sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/parallel-transform/download/parallel-transform-1.2.0.tgz", + "integrity": "sha1-kEnKN9bLIYLDsdLHIL6U0UpYFPw=", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npm.taobao.org/parse-asn1/download/parse-asn1-5.1.5.tgz", + "integrity": "sha1-ADJxND2ljclMrOSU+u89IUfs6g4=", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/parse-passwd/download/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz", + "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/pascalcase/download/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npm.taobao.org/path-browserify/download/path-browserify-0.0.1.tgz", + "integrity": "sha1-5sTd1+06onxoogzE5Q4aTug7vEo=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/path-dirname/download/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/path-type/download/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/pbkdf2/download/pbkdf2-3.1.1.tgz", + "integrity": "sha1-y4cksPramEWWhW0abrr9NYRlS5Q=", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true, + "optional": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz", + "integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=", + "dev": true, + "optional": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/pify/download/pify-4.0.1.tgz", + "integrity": "sha1-SyzSXFDVmHNcUCkiJP2MbfQeMjE=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-3.0.0.tgz", + "integrity": "sha1-J0kCDyOe2ZCIGx9xIQ1R62UjvqM=", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "portfinder": { + "version": "1.0.26", + "resolved": "https://registry.npm.taobao.org/portfinder/download/portfinder-1.0.26.tgz", + "integrity": "sha1-R1ZY1WyjC+1yrH8TeO01C9G2TnA=", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/posix-character-classes/download/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.23.tgz", + "integrity": "sha512-hOlMf3ouRIFXD+j2VJecwssTwbvsPGJVMzupptg+85WA+i7MwyrydmQAgY3R+m0Bc0exunhbJmijy8u8+vufuQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.1.tgz", + "integrity": "sha512-L2YKB3vF4PetdTIthQVeT+7YiSzMoNMLLYxPXXppOOP7NoazEAy45sh2LvJ8leCQjfBcfkYQs8TtCcQjeZTp8A==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "dev": true, + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "dev": true, + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "dev": true, + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-variant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "dev": true, + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz", + "integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0", + "postcss-value-parser": "^3.3.1" + } + }, + "postcss-modules-scope": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", + "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz", + "integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==", + "dev": true, + "requires": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^7.0.6" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "dev": true, + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "dev": true, + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", + "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npm.taobao.org/process/download/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz", + "integrity": "sha1-eCDZsWEgzFXKmud5JoCufbptf+I=", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz", + "integrity": "sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8=", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/prr/download/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz?cache=0&sync_timestamp=1585142991033&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpsl%2Fdownload%2Fpsl-1.8.0.tgz", + "integrity": "sha1-kyb4vPsBOtzABf3/BWrM4CDlHCQ=", + "dev": true, + "optional": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npm.taobao.org/public-encrypt/download/public-encrypt-4.0.3.tgz", + "integrity": "sha1-T8ydd6B+SLp1J+fL4N4z0HATMeA=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npm.taobao.org/bn.js/download/bn.js-4.11.9.tgz", + "integrity": "sha1-JtVWgpRY+dHoH8SJUkk9C6NQeCg=", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/pump/download/pump-3.0.0.tgz", + "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npm.taobao.org/pumpify/download/pumpify-1.5.1.tgz?cache=0&sync_timestamp=1569938200736&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpumpify%2Fdownload%2Fpumpify-1.5.1.tgz", + "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/pump/download/pump-2.0.1.tgz", + "integrity": "sha1-Ejma3W5M91Jtlzy8i1zi4pCLOQk=", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz", + "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=", + "dev": true, + "optional": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npm.taobao.org/querystring/download/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npm.taobao.org/querystring-es3/download/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/querystringify/download/querystringify-2.1.1.tgz", + "integrity": "sha1-YOWl/WSn+L+k0qsu1v30yFutFU4=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/randombytes/download/randombytes-2.1.0.tgz", + "integrity": "sha1-32+ENy8CcNxlzfYpE0mrekc9Tyo=", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/randomfill/download/randomfill-1.0.4.tgz", + "integrity": "sha1-ySGW/IarQr6YPxvzF3giSTHWFFg=", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz", + "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz", + "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.1.0.tgz", + "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=", + "dev": true + } + } + }, + "react-codemirror": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/react-codemirror/-/react-codemirror-1.0.0.tgz", + "integrity": "sha1-kUZ7U7H12A2Rai/QtMetuFqQAbo=", + "requires": { + "classnames": "^2.2.5", + "codemirror": "^5.18.2", + "create-react-class": "^15.5.1", + "lodash.debounce": "^4.0.8", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.5.4" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/read-pkg/download/read-pkg-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg%2Fdownload%2Fread-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fread-pkg-up%2Fdownload%2Fread-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/p-limit/download/p-limit-1.3.0.tgz", + "integrity": "sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg=", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz", + "integrity": "sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-2.2.1.tgz?cache=0&sync_timestamp=1584985910691&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-2.2.1.tgz", + "integrity": "sha1-DodiKjMlqjPokihcr4tOhGUppSU=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npm.taobao.org/regenerate/download/regenerate-1.4.1.tgz", + "integrity": "sha1-ytkq2Oa1kXc0hfvgWkhcr09Ffm8=", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npm.taobao.org/regenerate-unicode-properties/download/regenerate-unicode-properties-8.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerate-unicode-properties%2Fdownload%2Fregenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha1-5d5xEdZV57pgwFfb6f83yH5lzew=", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npm.taobao.org/regenerator-runtime/download/regenerator-runtime-0.13.5.tgz?cache=0&sync_timestamp=1584052597708&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-runtime%2Fdownload%2Fregenerator-runtime-0.13.5.tgz", + "integrity": "sha1-2Hih0JS0MG0QuQlkhLM+vVXiZpc=", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npm.taobao.org/regenerator-transform/download/regenerator-transform-0.14.5.tgz?cache=0&sync_timestamp=1593557570099&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregenerator-transform%2Fdownload%2Fregenerator-transform-0.14.5.tgz", + "integrity": "sha1-yY2hVGg2ccnE3LFuznNlF+G3/rQ=", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/regex-not/download/regex-not-1.0.2.tgz", + "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/regexp.prototype.flags/download/regexp.prototype.flags-1.3.0.tgz?cache=0&sync_timestamp=1576388379660&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexp.prototype.flags%2Fdownload%2Fregexp.prototype.flags-1.3.0.tgz", + "integrity": "sha1-erqJs8E6ZFCdq888qNn7ub31y3U=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "regexpu-core": { + "version": "4.7.0", + "resolved": "https://registry.npm.taobao.org/regexpu-core/download/regexpu-core-4.7.0.tgz?cache=0&sync_timestamp=1583949899397&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fregexpu-core%2Fdownload%2Fregexpu-core-4.7.0.tgz", + "integrity": "sha1-/L9FjFBDGwu3tF1pZ7gZLZHz2Tg=", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npm.taobao.org/regjsgen/download/regjsgen-0.5.2.tgz", + "integrity": "sha1-kv8pX7He7L9uzaslQ9IH6RqjNzM=", + "dev": true + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npm.taobao.org/regjsparser/download/regjsparser-0.6.4.tgz", + "integrity": "sha1-p2n4aEMIQBpm6bUp0kNv9NBmYnI=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npm.taobao.org/jsesc/download/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/remove-trailing-separator/download/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npm.taobao.org/repeat-element/download/repeat-element-1.1.3.tgz", + "integrity": "sha1-eC4NglwMWjuzlzH4Tv7mt0Lmsc4=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npm.taobao.org/repeat-string/download/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npm.taobao.org/request/download/request-2.88.2.tgz", + "integrity": "sha1-1zyRhzHLWofaBH4gcjQUb2ZNErM=", + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/require-directory/download/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/require-main-filename/download/require-main-filename-2.0.0.tgz", + "integrity": "sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs=", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/requires-port/download/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "reselect": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-3.0.1.tgz", + "integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-cwd/download/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/resolve-dir/download/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/global-modules/download/global-modules-1.0.0.tgz", + "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npm.taobao.org/resolve-url/download/resolve-url-0.2.1.tgz?cache=0&sync_timestamp=1585438700247&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve-url%2Fdownload%2Fresolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npm.taobao.org/ret/download/ret-0.1.15.tgz", + "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "dev": true + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npm.taobao.org/retry/download/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "right-pad": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz", + "integrity": "sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/ripemd160/download/ripemd160-2.0.2.tgz", + "integrity": "sha1-ocGm9iR1FXe6XQeRTLyShQWFiQw=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npm.taobao.org/run-queue/download/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/safe-regex/download/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/select-hose/download/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.7", + "resolved": "https://registry.npm.taobao.org/selfsigned/download/selfsigned-1.10.7.tgz", + "integrity": "sha1-2lgZ/QSdVXTyjoipvMbbxubzkGs=", + "dev": true, + "requires": { + "node-forge": "0.9.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz", + "integrity": "sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg=", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.1.tgz", + "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/serialize-javascript/download/serialize-javascript-3.1.0.tgz", + "integrity": "sha1-i/OpFwcSZk7yVhtEtpHq/jmSFOo=", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npm.taobao.org/serve-index/download/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz?cache=0&sync_timestamp=1593407647372&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz?cache=0&sync_timestamp=1563425414995&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsetprototypeof%2Fdownload%2Fsetprototypeof-1.1.0.tgz", + "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz", + "integrity": "sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk=", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/set-value/download/set-value-2.0.1.tgz?cache=0&sync_timestamp=1585775409029&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fset-value%2Fdownload%2Fset-value-2.0.1.tgz", + "integrity": "sha1-oY1AUw5vB95CKMfe/kInr4ytAFs=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz?cache=0&sync_timestamp=1563425414995&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsetprototypeof%2Fdownload%2Fsetprototypeof-1.1.1.tgz", + "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npm.taobao.org/sha.js/download/sha.js-2.4.11.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsha.js%2Fdownload%2Fsha.js-2.4.11.tgz", + "integrity": "sha1-N6XPC4HsvGlD3hCbopYNGyZYSuc=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/side-channel/download/side-channel-1.0.2.tgz", + "integrity": "sha1-310auttOS/SvHNiFK/Ey0veHaUc=", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/slash/download/slash-2.0.0.tgz", + "integrity": "sha1-3lUoUaF1nfOo8gZTVEL17E3eq0Q=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npm.taobao.org/snapdragon/download/snapdragon-0.8.2.tgz", + "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/extend-shallow/download/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/snapdragon-node/download/snapdragon-node-2.1.1.tgz", + "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-accessor-descriptor/download/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-1.0.0.tgz", + "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/is-descriptor/download/is-descriptor-1.0.2.tgz", + "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/snapdragon-util/download/snapdragon-util-3.0.1.tgz", + "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.20", + "resolved": "https://registry.npm.taobao.org/sockjs/download/sockjs-0.3.20.tgz", + "integrity": "sha1-smooPsVi74smh7RAM6Tuzqx12FU=", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + } + }, + "sockjs-client": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/sockjs-client/download/sockjs-client-1.4.0.tgz", + "integrity": "sha1-yfJWjhnI/YFztJl+o0IOC7MGx9U=", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz", + "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npm.taobao.org/faye-websocket/download/faye-websocket-0.11.3.tgz", + "integrity": "sha1-XA6aiWjokSwoZjn96XeosgnyUI4=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npm.taobao.org/source-list-map/download/source-list-map-2.0.1.tgz", + "integrity": "sha1-OZO9hzv8SEecyp6jpUeDXHwVSzQ=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.3.tgz?cache=0&sync_timestamp=1584831908370&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsource-map-resolve%2Fdownload%2Fsource-map-resolve-0.5.3.tgz", + "integrity": "sha1-GQhmvs51U+H48mei7oLGBrVQmho=", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npm.taobao.org/source-map-support/download/source-map-support-0.5.19.tgz?cache=0&sync_timestamp=1587719517036&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsource-map-support%2Fdownload%2Fsource-map-support-0.5.19.tgz", + "integrity": "sha1-qYti+G3K9PZzmWSMCFKRq56P7WE=", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npm.taobao.org/source-map-url/download/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.1.tgz?cache=0&sync_timestamp=1590161967473&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fspdx-correct%2Fdownload%2Fspdx-correct-3.1.1.tgz", + "integrity": "sha1-3s6BrJweZxPl99G28X1Gj6U9iak=", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npm.taobao.org/spdx-exceptions/download/spdx-exceptions-2.3.0.tgz?cache=0&sync_timestamp=1587422410312&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fspdx-exceptions%2Fdownload%2Fspdx-exceptions-2.3.0.tgz", + "integrity": "sha1-PyjOGnegA3JoPq3kpDMYNSeiFj0=", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha1-z3D1BILu/cmOPOCmgz5KU87rpnk=", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npm.taobao.org/spdx-license-ids/download/spdx-license-ids-3.0.5.tgz", + "integrity": "sha1-NpS1gEVnpFjTyARYQqY1hjL2JlQ=", + "dev": true + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/spdy/download/spdy-4.0.2.tgz", + "integrity": "sha1-t09GYgOj7aRSwCSSuR+56EonZ3s=", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/spdy-transport/download/spdy-transport-3.0.0.tgz", + "integrity": "sha1-ANSGOmQArXXfkzYaFghgXl3NzzE=", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npm.taobao.org/readable-stream/download/readable-stream-3.6.0.tgz", + "integrity": "sha1-M3u9o63AcGvT4CRCaihtS0sskZg=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/split-string/download/split-string-3.1.0.tgz", + "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npm.taobao.org/sshpk/download/sshpk-1.16.1.tgz", + "integrity": "sha1-+2YcC+8ps520B2nuOfpwCT1vaHc=", + "dev": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npm.taobao.org/ssri/download/ssri-6.0.1.tgz", + "integrity": "sha1-KjxBso3UW2K2Nnbst0ABJlrp7dg=", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npm.taobao.org/static-extend/download/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npm.taobao.org/define-property/download/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz?cache=0&sync_timestamp=1587328859420&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstatuses%2Fdownload%2Fstatuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/stream-browserify/download/stream-browserify-2.0.2.tgz?cache=0&sync_timestamp=1587041519870&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstream-browserify%2Fdownload%2Fstream-browserify-2.0.2.tgz", + "integrity": "sha1-h1IdOKRKp+6RzhzSpH3wy0ndZgs=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npm.taobao.org/stream-each/download/stream-each-1.2.3.tgz", + "integrity": "sha1-6+J6DDibBPvMIzZClS4Qcxr6m64=", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npm.taobao.org/stream-http/download/stream-http-2.8.3.tgz?cache=0&sync_timestamp=1588701035785&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstream-http%2Fdownload%2Fstream-http-2.8.3.tgz", + "integrity": "sha1-stJCRpKIpaJ+xP6JM6z2I95lFPw=", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.1.tgz?cache=0&sync_timestamp=1576147145118&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstream-shift%2Fdownload%2Fstream-shift-1.0.1.tgz", + "integrity": "sha1-1wiCgVWasneEJCebCHfaPDktWj0=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/string.prototype.matchall/download/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha1-SLtRAyb7n962ozzqqBpuoE73ZI4=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/string.prototype.trimend/download/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha1-hYEqa4R6wAInD1gIFGBkyZX7aRM=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/string.prototype.trimstart/download/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha1-FK9tnzSwU/fPyJty+PLuFLkDmlQ=", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npm.taobao.org/es-abstract/download/es-abstract-1.17.6.tgz?cache=0&sync_timestamp=1592109199190&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes-abstract%2Fdownload%2Fes-abstract-1.17.6.tgz", + "integrity": "sha1-kUIHFweFeyysx7iey2cDFsPi1So=", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/is-callable/download/is-callable-1.2.0.tgz?cache=0&sync_timestamp=1591427607174&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-callable%2Fdownload%2Fis-callable-1.2.0.tgz", + "integrity": "sha1-gzNlYLVKOONeOi33r9BFTWkUaLs=", + "dev": true + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.0.tgz", + "integrity": "sha1-7OOOOJ5JDfDcIcrqK9WW+Yf3Z/8=", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/strip-bom/download/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "style-loader": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz", + "integrity": "sha1-ofzMBrWNth/XpF2i2kT186Pme6I=", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npm.taobao.org/terser/download/terser-4.8.0.tgz?cache=0&sync_timestamp=1592448432005&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser%2Fdownload%2Fterser-4.8.0.tgz", + "integrity": "sha1-YwVjQ9fHC7KfOvZlhlpG/gOg3xc=", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.4", + "resolved": "https://registry.npm.taobao.org/terser-webpack-plugin/download/terser-webpack-plugin-1.4.4.tgz?cache=0&sync_timestamp=1592492230913&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterser-webpack-plugin%2Fdownload%2Fterser-webpack-plugin-1.4.4.tgz", + "integrity": "sha1-LGNUQ0cyS6r6mla6rd8WNMir/C8=", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^3.1.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz?cache=0&sync_timestamp=1593478693312&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fthrough2%2Fdownload%2Fthrough2-2.0.5.tgz", + "integrity": "sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/thunky/download/thunky-1.1.0.tgz?cache=0&sync_timestamp=1571043401546&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fthunky%2Fdownload%2Fthunky-1.1.0.tgz", + "integrity": "sha1-Wrr3FKlAXbBQRzK7zNLO3Z75U30=", + "dev": true + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npm.taobao.org/timers-browserify/download/timers-browserify-2.0.11.tgz", + "integrity": "sha1-gAsfPu4nLlvFPuRloE0OgEwxIR8=", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/to-fast-properties/download/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npm.taobao.org/to-object-path/download/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/to-regex/download/to-regex-3.0.2.tgz", + "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz", + "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.5.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftough-cookie%2Fdownload%2Ftough-cookie-2.5.0.tgz", + "integrity": "sha1-zZ+yoKodWhK0c72fuW+j3P9lreI=", + "dev": true, + "optional": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npm.taobao.org/tsconfig-paths/download/tsconfig-paths-3.9.0.tgz", + "integrity": "sha1-CYVHpsREiAfo/Ljq4IEGTumjyQs=", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/json5/download/json5-1.0.1.tgz?cache=0&sync_timestamp=1586046271069&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-1.0.1.tgz", + "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npm.taobao.org/tty-browserify/download/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npm.taobao.org/tunnel-agent/download/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npm.taobao.org/tweetnacl/download/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz", + "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "ua-parser-js": { + "version": "0.7.20", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", + "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/unicode-canonical-property-names-ecmascript/download/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha1-JhmADEyCWADv3YNDr33Zkzy+KBg=", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npm.taobao.org/unicode-match-property-ecmascript/download/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha1-jtKjJWmWG86SJ9Cc0/+7j+1fAgw=", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/unicode-match-property-value-ecmascript/download/unicode-match-property-value-ecmascript-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funicode-match-property-value-ecmascript%2Fdownload%2Funicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha1-DZH2AO7rMJaqlisdb8iIduZOpTE=", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npm.taobao.org/unicode-property-aliases-ecmascript/download/unicode-property-aliases-ecmascript-1.1.0.tgz?cache=0&sync_timestamp=1583945805856&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funicode-property-aliases-ecmascript%2Fdownload%2Funicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha1-3Vepn2IHvt/0Yoq++5TFDblByPQ=", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/union-value/download/union-value-1.0.1.tgz", + "integrity": "sha1-C2/nuDWuzaYcbqTU8CwUIh4QmEc=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npm.taobao.org/unique-filename/download/unique-filename-1.1.1.tgz", + "integrity": "sha1-HWl2k2mtoFgxA6HmrodoG1ZXMjA=", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npm.taobao.org/unique-slug/download/unique-slug-2.0.2.tgz", + "integrity": "sha1-uqvOkQg/xk6UWw861hPiZPfNTmw=", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npm.taobao.org/unset-value/download/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npm.taobao.org/has-value/download/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/isobject/download/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npm.taobao.org/has-values/download/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npm.taobao.org/upath/download/upath-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fupath%2Fdownload%2Fupath-1.2.0.tgz", + "integrity": "sha1-j2bbzVWog6za5ECK+LA1pQRMGJQ=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npm.taobao.org/urix/download/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npm.taobao.org/url/download/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npm.taobao.org/punycode/download/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npm.taobao.org/url-parse/download/url-parse-1.4.7.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Furl-parse%2Fdownload%2Furl-parse-1.4.7.tgz", + "integrity": "sha1-qKg1NejACjFuQDpdtKwbm4U64ng=", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/use/download/use-3.1.1.tgz", + "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npm.taobao.org/util/download/util-0.11.1.tgz?cache=0&sync_timestamp=1588238457176&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Futil%2Fdownload%2Futil-0.11.1.tgz", + "integrity": "sha1-MjZzNyDsZLsn9uJvQhqqLhtYjWE=", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npm.taobao.org/uuid/download/uuid-3.4.0.tgz?cache=0&sync_timestamp=1592944373333&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.4.0.tgz", + "integrity": "sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=", + "dev": true + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/v8-compile-cache/download/v8-compile-cache-2.1.1.tgz?cache=0&sync_timestamp=1590872707384&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fv8-compile-cache%2Fdownload%2Fv8-compile-cache-2.1.1.tgz", + "integrity": "sha1-VLw83UMxe8qR413K8wWxpyN950U=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha1-/JH2uce6FchX9MssXe/uw51PQQo=", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npm.taobao.org/verror/download/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz?cache=0&sync_timestamp=1572870776965&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvm-browserify%2Fdownload%2Fvm-browserify-1.1.2.tgz", + "integrity": "sha1-eGQcSIuObKkadfUR56OzKobl3aA=", + "dev": true + }, + "watchpack": { + "version": "1.7.2", + "resolved": "https://registry.npm.taobao.org/watchpack/download/watchpack-1.7.2.tgz", + "integrity": "sha1-wC5NTUmRPD5+EiwzJTZa+dMx6ao=", + "dev": true, + "requires": { + "chokidar": "^3.4.0", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz", + "integrity": "sha1-xV7PAhheJGklk5kxDBc84xIzsUI=", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.1.0.tgz?cache=0&sync_timestamp=1593261331793&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.1.0.tgz", + "integrity": "sha1-MPpAyef+B9vIlWeM0ocCTeokHdk=", + "dev": true, + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.0", + "resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-3.4.0.tgz?cache=0&sync_timestamp=1587911196018&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.4.0.tgz", + "integrity": "sha1-swYRQjzjdjV8dlubj5BLn7o8C+g=", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", + "dev": true, + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.3.tgz?cache=0&sync_timestamp=1588787369955&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.1.3.tgz", + "integrity": "sha1-+3OHA66NL5/pAMM4Nt3r7ouX8j4=", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npm.taobao.org/glob-parent/download/glob-parent-5.1.1.tgz", + "integrity": "sha1-tsHvQXxOVmPqSY8cRa+saRa7wik=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=", + "dev": true, + "optional": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-3.4.0.tgz?cache=0&sync_timestamp=1584985910691&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freaddirp%2Fdownload%2Freaddirp-3.4.0.tgz", + "integrity": "sha1-n9zN+ekVWAVEkiGsZF6DA6tbmto=", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/watchpack-chokidar2/download/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha1-mUihhmy71suCTeoTp+1pH2yN3/A=", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npm.taobao.org/wbuf/download/wbuf-1.7.3.tgz", + "integrity": "sha1-wdjRSTFtPqhShIiVy2oL/oh7h98=", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "webpack": { + "version": "4.43.0", + "resolved": "https://registry.npm.taobao.org/webpack/download/webpack-4.43.0.tgz?cache=0&sync_timestamp=1593446690098&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack%2Fdownload%2Fwebpack-4.43.0.tgz", + "integrity": "sha1-xIVHsR1WMiTFYdrRFyyKoLimeOY=", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.6.1", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npm.taobao.org/acorn/download/acorn-6.4.1.tgz?cache=0&sync_timestamp=1591869432510&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn%2Fdownload%2Facorn-6.4.1.tgz", + "integrity": "sha1-Ux5Yuj9RudrLmmZGyk3r9bFMpHQ=", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz", + "integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.5.tgz?cache=0&sync_timestamp=1587535418745&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-0.5.5.tgz", + "integrity": "sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8=", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npm.taobao.org/webpack-cli/download/webpack-cli-3.3.12.tgz?cache=0&sync_timestamp=1592482091989&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebpack-cli%2Fdownload%2Fwebpack-cli-3.3.12.tgz", + "integrity": "sha1-lOmtoIFFPNCqYJyZ5QABL9OtLUo=", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/emojis-list/download/emojis-list-3.0.0.tgz", + "integrity": "sha1-VXBmIEatKeLpFucariYKvf9Pang=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npm.taobao.org/json5/download/json5-1.0.1.tgz?cache=0&sync_timestamp=1586046271069&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fjson5%2Fdownload%2Fjson5-1.0.1.tgz", + "integrity": "sha1-d5+wAYYE+oVOrL9iUhgNg1Q+Pb4=", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npm.taobao.org/loader-utils/download/loader-utils-1.4.0.tgz", + "integrity": "sha1-xXm140yzSxp07cbB+za/o3HVphM=", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npm.taobao.org/webpack-dev-middleware/download/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha1-ABnD23FuP6XOy/ZPKriKdLqzMfM=", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npm.taobao.org/mime/download/mime-2.4.6.tgz?cache=0&sync_timestamp=1590596706367&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-2.4.6.tgz", + "integrity": "sha1-5bQHyQ20QvK+tbFiNz0Htpr/pNE=", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.11.0", + "resolved": "https://registry.npm.taobao.org/webpack-dev-server/download/webpack-dev-server-3.11.0.tgz", + "integrity": "sha1-jxVKO84bz9HMYY705wMniFXn/4w=", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npm.taobao.org/debug/download/debug-4.1.1.tgz", + "integrity": "sha1-O3ImAlUQnGtYnO4FDx1RYTlmR5E=", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", + "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz", + "integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npm.taobao.org/supports-color/download/supports-color-6.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-6.1.0.tgz", + "integrity": "sha1-B2Srxpxj1ayELdSGfo0CXogN+PM=", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/webpack-log/download/webpack-log-2.0.0.tgz", + "integrity": "sha1-W3ko4GN1k/EZ0y9iJ8HgrDHhtH8=", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npm.taobao.org/webpack-sources/download/webpack-sources-1.4.3.tgz", + "integrity": "sha1-7t2OwLko+/HL/plOItLYkPMwqTM=", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true + } + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npm.taobao.org/websocket-driver/download/websocket-driver-0.6.5.tgz?cache=0&sync_timestamp=1591288600527&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwebsocket-driver%2Fdownload%2Fwebsocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npm.taobao.org/websocket-extensions/download/websocket-extensions-0.1.4.tgz", + "integrity": "sha1-f4RzvIOd/YdgituV1+sHUhFXikI=", + "dev": true + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/which-module/download/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wolfy87-eventemitter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/wolfy87-eventemitter/-/wolfy87-eventemitter-5.2.8.tgz", + "integrity": "sha512-Z+wAU9SyuOZgFj22zBl8sg0auJOkrKBZl8TTlEM5dRDDs2zPtlm72vPJUIlf6tUJ4w2JLgrF7VznRnQHP+Rn/Q==" + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npm.taobao.org/worker-farm/download/worker-farm-1.7.0.tgz", + "integrity": "sha1-JqlMU5G7ypJhUgAvabhKS/dy5ag=", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-5.1.0.tgz?cache=0&sync_timestamp=1587574768060&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwrap-ansi%2Fdownload%2Fwrap-ansi-5.1.0.tgz", + "integrity": "sha1-H9H2cjXVttD+54EFYAG/tpTAOwk=", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-4.1.0.tgz", + "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/string-width/download/string-width-3.1.0.tgz", + "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz", + "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npm.taobao.org/ws/download/ws-6.2.1.tgz", + "integrity": "sha1-RC/fCkftZPWbal2P8TD0dI7VJPs=", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz", + "integrity": "sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz", + "integrity": "sha1-le+U+F7MgdAHwmThkKEg8KPIVms=", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz", + "integrity": "sha1-27fa+b/YusmrRev2ArjLrQ1dCP0=", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npm.taobao.org/yargs/download/yargs-13.3.2.tgz?cache=0&sync_timestamp=1593578283566&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs%2Fdownload%2Fyargs-13.3.2.tgz", + "integrity": "sha1-rX/+/sGqWVZayRX4Lcyzipwxot0=", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-4.1.0.tgz", + "integrity": "sha1-i5+PCM8ay4Q3Vqg5yox+MWjFGZc=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npm.taobao.org/string-width/download/string-width-3.1.0.tgz", + "integrity": "sha1-InZ74htirxCBV0MG9prFG2IgOWE=", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-5.2.0.tgz", + "integrity": "sha1-jJpTb+tq/JYr36WxBKUJHBrZwK4=", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-13.1.2.tgz", + "integrity": "sha1-Ew8JcC667vJlDVTObj5XBvek+zg=", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/saga/seata-saga-statemachine-designer/package.json b/saga/seata-saga-statemachine-designer/package.json new file mode 100644 index 0000000..2efa0c7 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/package.json @@ -0,0 +1,94 @@ +{ + "name": "seata-saga-statemachine-designer", + "version": "0.0.1", + "description": "A visual graph designer for Seata Saga StateMachine based on GGEdior 2.0.4", + "keywords": [ + "react", + "graphics", + "designer", + "seata", + "saga", + "state machine" + ], + "main": "cjs/index.js", + "module": "es/index.js", + "types": "typings/index.d.ts", + "files": [ + "src", + "es", + "cjs", + "dist", + "*.md", + "typings" + ], + "scripts": { + "start": "webpack-dev-server --config ./tools/webpack.config.dev.js --open", + "build": "rimraf ./dist && webpack --config ./tools/webpack.config.prod.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/seata/seata.git" + }, + "authors": [ + { + "name": "陈龙", + "email": "long_187@126.com" + }, + { + "name": "高力", + "email": "3071730@qq.com" + } + ], + "license": "Apache License, Version 2.0", + "bugs": { + "url": "https://github.com/seata/seata/issues" + }, + "homepage": "http://seata.io/", + "peerDependencies": { + "react": "^16.3.0" + }, + "dependencies": { + "@antv/g6": "^2.2.6", + "codemirror": "^5.55.0", + "core-js": "^3.6.5", + "lodash": "^4.17.10", + "react-codemirror": "^1.0.0" + }, + "devDependencies": { + "@babel/cli": "^7.10.4", + "@babel/core": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-runtime": "^7.10.4", + "@babel/polyfill": "^7.10.4", + "@babel/preset-env": "^7.10.4", + "@babel/preset-react": "^7.10.4", + "@babel/runtime": "^7.10.4", + "babel-eslint": "^10.1.0", + "babel-loader": "^8.1.0", + "babel-plugin-module-resolver": "^3.1.1", + "babel-plugin-transform-inline-environment-variables": "^0.4.3", + "cross-env": "^5.2.0", + "css-loader": "^2.1.0", + "cz-conventional-changelog": "^2.1.0", + "eslint": "^5.11.1", + "eslint-config-airbnb": "^17.1.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.20.3", + "less": "^3.11.3", + "less-loader": "^4.1.0", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.5.0", + "rimraf": "^2.6.2", + "style-loader": "^0.23.1", + "webpack": "^4.43.0", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + } +} diff --git a/saga/seata-saga-statemachine-designer/src/Flow/WorkSpace.js b/saga/seata-saga-statemachine-designer/src/Flow/WorkSpace.js new file mode 100644 index 0000000..cd9d26c --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/Flow/WorkSpace.js @@ -0,0 +1,125 @@ +import React from 'react'; +import { withPropsAPI, Flow } from 'gg-editor'; +import styles from './index.less'; +import CodeMirror from 'react-codemirror'; +import 'codemirror/lib/codemirror.css' +import 'codemirror/mode/javascript/javascript' + +const nodeIndexes = { + Start: 1, + ServiceTask: 1, + ScriptTask: 1, + Compensation: 1, + Choice: 1, + Succeed: 1, + Fail: 1, + Catch: 1, + CompensationTrigger: 1, + SubStateMachine: 1 +} + +class WorkSpaceBase extends React.Component { + + toGGEditorData(dataMap) { + const data = { nodes: [], edges: [] }; + Object.values(dataMap).map(value => { + + if (value.type && value.type == 'node') { + data.nodes[data.nodes.length] = value; + } + else if (value.source && value.target) { + data.edges[data.edges.length] = value; + } + }); + return data; + }; + + changeFlowData(param) { + const { propsAPI, setFlowData } = this.props; + const { executeCommand, update } = propsAPI; + + if (param.action == 'add' && param.item.type == 'edge') { + // Default polyline-round type @FIXME polyline-round has a bug + if (param.item.target + && param.item.target.model + && param.item.source + && param.item.source.model) { + executeCommand(() => { + update(param.item, { + shape: 'flow-polyline-round', + }); + }); + } + if (param.item.target && param.item.target.model && param.item.target.model.stateType == 'Compensation') { + executeCommand(() => { + update(param.item, { + style: { + lineDash: "4", + }, + type: 'Compensation', + }); + }); + } + else if (param.item.source && param.item.source.model) { + if (param.item.source.model.stateType == 'Choice') { + const choiceLinePropsTemplate = { + "Expression": "", + "Default": false + }; + executeCommand(() => { + update(param.item, { + stateProps: choiceLinePropsTemplate + }); + }); + } + else if (param.item.source.model.stateType == 'Catch') { + const catchLinePropsTemplate = { + "Exceptions": ["java.lang.Throwable"] + }; + executeCommand(() => { + update(param.item, { + stateProps: catchLinePropsTemplate + }); + }); + } + } + } + + if (param.action == 'add' && param.item.type == 'node' && param.item.model) { + param.item.model.stateId = param.item.model.stateId + nodeIndexes[param.item.model.stateType]++; + if (param.item.model.stateType == 'ServiceTask' + || param.item.model.stateType == 'Compensation' + || param.item.model.stateType == 'ScriptTask' + || param.item.model.stateType == 'SubStateMachine') { + param.item.model.label = param.item.model.stateId; + } + if (param.item.model.stateType == 'SubStateMachine') { + executeCommand(() => { + update(param.item, { + style: { + lineWidth: 2, + }, + }); + }); + } + } + + param.item && setFlowData(this.toGGEditorData(param.item.dataMap)); + }; + + render() { + const { showJson, flowData, setFlowData } = this.props; + + return <> + {showJson && { + setFlowData(JSON.parse(newValue)); + }}>} + + + } +} + +export const WorkSpace = withPropsAPI(WorkSpaceBase) \ No newline at end of file diff --git a/saga/seata-saga-statemachine-designer/src/Flow/index.js b/saga/seata-saga-statemachine-designer/src/Flow/index.js new file mode 100644 index 0000000..2c64a6a --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/Flow/index.js @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { Row, Col } from 'antd'; +import GGEditor from 'gg-editor'; +import EditorMinimap from '../components/EditorMinimap'; +import { FlowContextMenu } from '../components/EditorContextMenu'; +import { FlowToolbar } from '../components/EditorToolbar'; +import { FlowItemPanel } from '../components/EditorItemPanel'; +import { FlowDetailPanel } from '../components/EditorDetailPanel'; +import styles from './index.less'; +import { WorkSpace } from './WorkSpace'; + +const FlowPage = () => { + + const [showJson, setShowJson] = useState(false); + const [flowData, setFlowData] = useState({}); + + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default FlowPage; diff --git a/saga/seata-saga-statemachine-designer/src/Flow/index.less b/saga/seata-saga-statemachine-designer/src/Flow/index.less new file mode 100644 index 0000000..dc114e9 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/Flow/index.less @@ -0,0 +1,57 @@ +.editor { + display: flex; + flex-direction: column; + height: 100vh; + background: #fff; +} + +.editorHd { + padding: 8px; + border: 1px solid #e6e9ed; +} + +.editorBd { + flex: 1; + overflow: auto; +} + +.editorSidebar, +.editorContent { + display: flex; + flex-direction: column; +} + +.editorSidebar { + background: #fafafa; + + &:first-child { + border-right: 1px solid #e6e9ed; + } + + &:last-child { + border-left: 1px solid #e6e9ed; + } +} + +.flow, +.mind, +.koni { + flex: 1; + // https://github.com/philipwalton/flexbugs/issues/197#issuecomment-378908438 + height: 0; +} + +.hidden { + display: none; +} + +:global { + .ReactCodeMirror { + flex: 1; + + .CodeMirror { + height: 100%; + } + } +} + diff --git a/saga/seata-saga-statemachine-designer/src/common/IconFont/index.js b/saga/seata-saga-statemachine-designer/src/common/IconFont/index.js new file mode 100644 index 0000000..cc5b126 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/common/IconFont/index.js @@ -0,0 +1,13 @@ +import { Icon } from 'antd'; + +const IconFont = Icon.createFromIconfontCN({ + scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js', +}); + +export default IconFont; + +export const IconFontExt = Icon.createFromIconfontCN({ + scriptUrl: 'https://at.alicdn.com/t/font_1529997_6nr9bramkvp.js', +}); + + diff --git a/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/FlowContextMenu.js b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/FlowContextMenu.js new file mode 100644 index 0000000..c29e1a0 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/FlowContextMenu.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { NodeMenu, EdgeMenu, GroupMenu, MultiMenu, CanvasMenu, ContextMenu } from 'gg-editor'; +import MenuItem from './MenuItem'; +import styles from './index.less'; + +const FlowContextMenu = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default FlowContextMenu; diff --git a/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/MenuItem.js b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/MenuItem.js new file mode 100644 index 0000000..09f5b46 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/MenuItem.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Command } from 'gg-editor'; +import upperFirst from 'lodash/upperFirst'; +import IconFont from '../../common/IconFont'; +import styles from './index.less'; + +const MenuItem = (props) => { + const { command, icon, text } = props; + + return ( + +
    + + {text || upperFirst(command)} +
    +
    + ); +}; + +export default MenuItem; diff --git a/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.js b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.js new file mode 100644 index 0000000..a301249 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.js @@ -0,0 +1,3 @@ +import FlowContextMenu from './FlowContextMenu'; + +export { FlowContextMenu }; diff --git a/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.less b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.less new file mode 100644 index 0000000..8a2cdae --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/components/EditorContextMenu/index.less @@ -0,0 +1,39 @@ +.contextMenu { + display: none; + overflow: hidden; + background: #fff; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + + .item { + display: flex; + align-items: center; + padding: 5px 12px; + cursor: pointer; + transition: all 0.3s; + user-select: none; + + &:hover { + background: #e6f7ff; + } + + i { + margin-right: 8px; + } + } + + :global { + .disable { + :local { + .item { + color: rgba(0, 0, 0, 0.25); + cursor: auto; + + &:hover { + background: #fff; + } + } + } + } + } +} diff --git a/saga/seata-saga-statemachine-designer/src/components/EditorDetailPanel/DetailForm.js b/saga/seata-saga-statemachine-designer/src/components/EditorDetailPanel/DetailForm.js new file mode 100644 index 0000000..3f65a10 --- /dev/null +++ b/saga/seata-saga-statemachine-designer/src/components/EditorDetailPanel/DetailForm.js @@ -0,0 +1,182 @@ +import React, { Fragment } from 'react'; +import { Card, Form, Input, Select, Button } from 'antd'; +import { withPropsAPI } from 'gg-editor'; +import upperFirst from 'lodash/upperFirst'; + +const { Item } = Form; +const { Option } = Select; +const { TextArea } = Input; + +const inlineFormItemLayout = { + labelCol: { + sm: { span: 5 }, + }, + wrapperCol: { + sm: { span: 19 }, + }, +}; + +let lastSelectedItem; + +class DetailForm extends React.Component { + get item() { + const { propsAPI } = this.props; + + return propsAPI.getSelected()[0] ? propsAPI.getSelected()[0] : lastSelectedItem; + } + + componentWillMount() { + // fix edit node details + this.operationDetail(); + } + + operationDetail = () => { + const { propsAPI, form } = this.props; + const currentPage = propsAPI.editor.getCurrentPage().getGraph(); + currentPage.on('node:click', (e) => { + const { label, stateId, stateType, stateProps } = e.item.getModel(); + lastSelectedItem = e.item; + form.setFieldsValue({ label, stateId, stateType, stateProps: JSON.stringify(stateProps, null, 2) }); + }); + currentPage.on('edge:click', (e) => { + const { label = '', shape = 'flow-smooth', stateProps } = e.item.getModel(); + lastSelectedItem = e.item; + form.setFieldsValue({ label, shape, stateProps: JSON.stringify(stateProps, null, 2) }); + }); + } + + handleSubmit = (e) => { + if (e && e.preventDefault) { + e.preventDefault(); + } + + const { form, propsAPI } = this.props; + const { executeCommand, update } = propsAPI; + + setTimeout(() => { + form.validateFieldsAndScroll((err, values) => { + if (err) { + return; + } + + const item = this.item; + + if (!item) { + return; + } + + if (values.stateProps) { + values.stateProps = JSON.parse(values.stateProps); + } + + lastSelectedItem = item; + + executeCommand(() => { + update(item, { + ...values, + }); + }); + }); + }, 0); + }; + + renderEdgeShapeSelect = () => { + return ( + + ); + }; + + renderNodeDetail = () => { + const { form } = this.props; + const { label, stateId, stateType, stateProps } = this.item.getModel(); + + return ( + + + {form.getFieldDecorator('label', { + initialValue: label, + })()} + + + {form.getFieldDecorator('stateId', { + initialValue: stateId, + })()} + + + {form.getFieldDecorator('stateType', { + initialValue: stateType, + })()} + + + {form.getFieldDecorator('stateProps', { + initialValue: JSON.stringify(stateProps, null, 2), + })(