├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── 1_feature_request.md │ ├── 2_enhancement_request.md │ └── 3_bug_report.md ├── dependabot.yml ├── release-notes.yml └── workflows │ ├── add-to-project.yml │ ├── dependabot-automation.yml │ ├── docs.yml │ ├── main.yml │ ├── pullrequest.yml │ └── release-notes.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── coverage-report └── pom.xml ├── docs ├── README.md ├── _playbook │ ├── .gitignore │ ├── .vale.ini │ ├── package.json │ └── playbook.yaml └── reference │ ├── antora.yml │ └── modules │ ├── ROOT │ └── pages │ │ ├── commands.adoc │ │ ├── events.adoc │ │ ├── index.adoc │ │ ├── queries.adoc │ │ └── release-notes.adoc │ └── nav.adoc ├── kotlin-test ├── pom.xml └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── axonframework │ │ └── extension │ │ └── kotlin │ │ └── test │ │ └── FixtureExtensions.kt │ └── test │ └── kotlin │ └── org │ └── axonframework │ └── extension │ └── kotlin │ └── test │ ├── FixtureExtensionsTest.kt │ └── testObjects.kt ├── kotlin ├── pom.xml └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── axonframework │ │ │ └── extensions │ │ │ └── kotlin │ │ │ ├── AggregateLifecycleExtensions.kt │ │ │ ├── BuilderExtensions.kt │ │ │ ├── CommandGatewayExtensions.kt │ │ │ ├── EventUpcaster.kt │ │ │ ├── QueryGatewayExtensions.kt │ │ │ ├── QueryUpdateEmitterExtensions.kt │ │ │ ├── ResultDiscriminatorCommandCallback.kt │ │ │ ├── messaging │ │ │ └── responsetypes │ │ │ │ └── ArrayResponseType.kt │ │ │ └── serialization │ │ │ ├── AxonSerializers.kt │ │ │ ├── KotlinSerializer.kt │ │ │ └── MetaDataSerializer.kt │ └── resources │ │ └── META-INF │ │ └── spring-devtools.properties │ └── test │ └── kotlin │ └── org │ └── axonframework │ └── extensions │ └── kotlin │ ├── CommandGatewayExtensionsTest.kt │ ├── QueryGatewayExtensionsTest.kt │ ├── QueryUpdateEmitterExtensionsTest.kt │ ├── ResultDiscriminatorCommandCallbackTest.kt │ ├── mockkExtensions.kt │ ├── serializer │ ├── AxonSerializersTest.kt │ ├── KotlinSerializerCborTest.kt │ ├── KotlinSerializerJsonTest.kt │ ├── KotlinSerializerProtobufTest.kt │ ├── MetaDataSerializerTest.kt │ ├── hexExtensions.kt │ └── testTypes.kt │ └── testObjects.kt ├── mvnw ├── mvnw.cmd └── pom.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = false 9 | max_line_length = 180 10 | tab_width = 4 11 | ij_continuation_indent_size = 8 12 | ij_formatter_off_tag = @formatter:off 13 | ij_formatter_on_tag = @formatter:on 14 | ij_formatter_tags_enabled = true 15 | ij_smart_tabs = false 16 | ij_wrap_on_typing = false 17 | 18 | [{*.pom,*.xml}] 19 | indent_size = 2 20 | # ij_xml_align_attributes = true 21 | # ij_xml_align_text = false 22 | # ij_xml_attribute_wrap = normal 23 | # ij_xml_block_comment_at_first_column = true 24 | # ij_xml_keep_blank_lines = 2 25 | # ij_xml_keep_indents_on_empty_lines = false 26 | # ij_xml_keep_line_breaks = true 27 | # ij_xml_keep_line_breaks_in_text = true 28 | # ij_xml_keep_whitespaces = false 29 | # ij_xml_keep_whitespaces_around_cdata = preserve 30 | # ij_xml_keep_whitespaces_inside_cdata = false 31 | # ij_xml_line_comment_at_first_column = true 32 | # ij_xml_space_after_tag_name = false 33 | # ij_xml_space_around_equals_in_attribute = false 34 | # ij_xml_space_inside_empty_tag = false 35 | # ij_xml_text_wrap = normal 36 | # ij_xml_use_custom_settings = false 37 | 38 | [{*.gradle.kts,*.kt,*.kts,*.main.kts}] 39 | # indent_size = 4 40 | # tab_width = 4 41 | # ij_continuation_indent_size = 4 42 | # ij_kotlin_align_in_columns_case_branch = false 43 | # ij_kotlin_align_multiline_binary_operation = false 44 | # ij_kotlin_align_multiline_extends_list = false 45 | # ij_kotlin_align_multiline_method_parentheses = false 46 | # ij_kotlin_align_multiline_parameters = true 47 | # ij_kotlin_align_multiline_parameters_in_calls = false 48 | # ij_kotlin_allow_trailing_comma = false 49 | # ij_kotlin_allow_trailing_comma_on_call_site = false 50 | # ij_kotlin_assignment_wrap = off 51 | # ij_kotlin_blank_lines_after_class_header = 0 52 | # ij_kotlin_blank_lines_around_block_when_branches = 0 53 | # ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 54 | # ij_kotlin_block_comment_at_first_column = true 55 | # ij_kotlin_call_parameters_new_line_after_left_paren = false 56 | # ij_kotlin_call_parameters_right_paren_on_new_line = false 57 | # ij_kotlin_call_parameters_wrap = off 58 | # ij_kotlin_catch_on_new_line = false 59 | # ij_kotlin_class_annotation_wrap = split_into_lines 60 | # ij_kotlin_continuation_indent_for_chained_calls = true 61 | # ij_kotlin_continuation_indent_for_expression_bodies = true 62 | # ij_kotlin_continuation_indent_in_argument_lists = true 63 | # ij_kotlin_continuation_indent_in_elvis = true 64 | # ij_kotlin_continuation_indent_in_if_conditions = true 65 | # ij_kotlin_continuation_indent_in_parameter_lists = true 66 | # ij_kotlin_continuation_indent_in_supertype_lists = true 67 | # ij_kotlin_else_on_new_line = false 68 | # ij_kotlin_enum_constants_wrap = off 69 | # ij_kotlin_extends_list_wrap = off 70 | # ij_kotlin_field_annotation_wrap = split_into_lines 71 | # ij_kotlin_finally_on_new_line = false 72 | # ij_kotlin_if_rparen_on_new_line = false 73 | # ij_kotlin_import_nested_classes = false 74 | # ij_kotlin_insert_whitespaces_in_simple_one_line_method = true 75 | # ij_kotlin_keep_blank_lines_before_right_brace = 2 76 | # ij_kotlin_keep_blank_lines_in_code = 2 77 | # ij_kotlin_keep_blank_lines_in_declarations = 2 78 | # ij_kotlin_keep_first_column_comment = true 79 | # ij_kotlin_keep_indents_on_empty_lines = false 80 | # ij_kotlin_keep_line_breaks = true 81 | # ij_kotlin_lbrace_on_next_line = false 82 | # ij_kotlin_line_comment_add_space = false 83 | # ij_kotlin_line_comment_at_first_column = true 84 | # ij_kotlin_method_annotation_wrap = split_into_lines 85 | # ij_kotlin_method_call_chain_wrap = off 86 | # ij_kotlin_method_parameters_new_line_after_left_paren = false 87 | # ij_kotlin_method_parameters_right_paren_on_new_line = false 88 | # ij_kotlin_method_parameters_wrap = off 89 | # ij_kotlin_name_count_to_use_star_import = 5 90 | # ij_kotlin_name_count_to_use_star_import_for_members = 3 91 | # ij_kotlin_parameter_annotation_wrap = off 92 | # ij_kotlin_space_after_comma = true 93 | # ij_kotlin_space_after_extend_colon = true 94 | # ij_kotlin_space_after_type_colon = true 95 | # ij_kotlin_space_before_catch_parentheses = true 96 | # ij_kotlin_space_before_comma = false 97 | # ij_kotlin_space_before_extend_colon = true 98 | # ij_kotlin_space_before_for_parentheses = true 99 | # ij_kotlin_space_before_if_parentheses = true 100 | # ij_kotlin_space_before_lambda_arrow = true 101 | # ij_kotlin_space_before_type_colon = false 102 | # ij_kotlin_space_before_when_parentheses = true 103 | # ij_kotlin_space_before_while_parentheses = true 104 | # ij_kotlin_spaces_around_additive_operators = true 105 | # ij_kotlin_spaces_around_assignment_operators = true 106 | # ij_kotlin_spaces_around_equality_operators = true 107 | # ij_kotlin_spaces_around_function_type_arrow = true 108 | # ij_kotlin_spaces_around_logical_operators = true 109 | # ij_kotlin_spaces_around_multiplicative_operators = true 110 | # ij_kotlin_spaces_around_range = false 111 | # ij_kotlin_spaces_around_relational_operators = true 112 | # ij_kotlin_spaces_around_unary_operator = false 113 | # ij_kotlin_spaces_around_when_arrow = true 114 | # ij_kotlin_variable_annotation_wrap = off 115 | # ij_kotlin_while_on_new_line = false 116 | # ij_kotlin_wrap_elvis_expressions = 1 117 | # ij_kotlin_wrap_expression_body_functions = 0 118 | # ij_kotlin_wrap_first_method_in_call_chain = false 119 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.java text diff=java 7 | *.html text diff=html 8 | *.properties text 9 | *.xml text 10 | *.txt text 11 | *.md text 12 | *.css text 13 | *.js text 14 | *.sql text 15 | core/src/main/resources/META-INF/services/org.axonframework.serialization.ContentTypeConverter text -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @AxonFramework/framework-developers -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Thank you for your interest in contributing to the Axon Framework. To make sure using Axon is a smooth experience for 4 | everybody, we've set up a number of guidelines to follow. 5 | 6 | There are different ways in which you can contribute to the framework: 7 | 8 | 1. You can report any bugs, feature requests or ideas about improvements on GitHub: https://github.com/AxonFramework/extension-kotlin/issues/. 9 | All ideas are welcome. Please be as exact as possible when reporting bugs. This will help us reproduce and thus solve 10 | the problem faster. 11 | 2. If you have created a component for your own application that you think might be useful to include in the 12 | framework, send us a pull request (or patch or a zip containing the source code). We will evaluate it and try to 13 | fit it in the framework. Please make sure code is properly documented using javadoc. This helps us to understand 14 | what is going on. 15 | 3. If you know of any other way you think you can help us, do not hesitate to join and start a conversation on 16 | [AxonIQ's discussion platform](https://discuss.axoniq.io/). 17 | 18 | ## Code contributions 19 | 20 | If you're contributing code, please take care of the following: 21 | 22 | ### Contributor Licence Agreement 23 | 24 | To keep everyone out of trouble (both you and us), we require that all contributors (digitally) sign a Contributor 25 | License Agreement. Basically, the agreement says that we may freely use the code you contribute to the Axon Framework, 26 | and that we won't hold you liable for any unfortunate side effects that the code may cause. 27 | 28 | To sign the CLA, visit: https://www.clahub.com/agreements/AxonFramework/AxonFramework. 29 | 30 | ### Code style 31 | 32 | We're trying very hard to maintain a consistent style of coding throughout the code base. Think of things like 33 | indenting using 4 spaces, putting opening brackets (the '{') on the same line and putting proper Javadoc on all 34 | non-private members. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Feature request' 3 | about: 'Suggest a feature for the Kotlin Extension' 4 | title: 5 | labels: 'Type: Feature' 6 | --- 7 | 8 | 9 | 10 | ### Feature Description 11 | 12 | 16 | 17 | ### Current Behaviour 18 | 19 | 20 | 21 | ### Wanted Behaviour 22 | 23 | 24 | 25 | ### Possible Workarounds 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2_enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Enhancement request' 3 | about: 'Suggest an enhancement/change to an existing feature for the Kotlin Extension' 4 | title: 5 | labels: 'Type: Enhancement' 6 | --- 7 | 8 | 9 | 10 | ### Enhancement Description 11 | 12 | 13 | 14 | ### Current Behaviour 15 | 16 | 17 | 18 | ### Wanted Behaviour 19 | 20 | 21 | 22 | ### Possible Workarounds 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3_bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug report' 3 | about: 'Report a bug for the Kotlin Extension' 4 | title: 5 | labels: 'Type: Bug' 6 | --- 7 | 8 | 9 | 10 | ### Basic information 11 | 12 | * Axon Framework version: 13 | * JDK version: 14 | * Kotlin Extension version: 15 | * Complete executable reproducer if available (e.g. GitHub Repo): 16 | 17 | ### Steps to reproduce 18 | 19 | 23 | 24 | ### Expected behaviour 25 | 26 | 27 | 28 | ### Actual behaviour 29 | 30 | 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | day: "sunday" 9 | open-pull-requests-limit: 5 10 | labels: 11 | - "Type: Dependency Upgrade" 12 | - "Priority 1: Must" 13 | milestone: 10 14 | groups: 15 | github-dependencies: 16 | update-types: 17 | - "patch" 18 | - "minor" 19 | - "major" 20 | 21 | - package-ecosystem: maven 22 | directory: "/" 23 | schedule: 24 | interval: weekly 25 | day: "sunday" 26 | open-pull-requests-limit: 10 27 | labels: 28 | - "Type: Dependency Upgrade" 29 | - "Priority 1: Must" 30 | milestone: 10 31 | groups: 32 | maven-dependencies: 33 | update-types: 34 | - "patch" 35 | - "minor" 36 | - "major" -------------------------------------------------------------------------------- /.github/release-notes.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | sections: 3 | - title: ":star: Features" 4 | labels: [ "Type: Feature" ] 5 | - title: ":chart_with_upwards_trend: Enhancements" 6 | labels: [ "Type: Enhancement" ] 7 | - title: ":beetle: Bug Fixes" 8 | labels: [ "Type: Bug" ] 9 | - title: ":hammer_and_wrench: Dependency Upgrade" 10 | labels: [ "Type: Dependency Upgrade" ] 11 | issues: 12 | exclude: 13 | labels: [ "Type: Incorrect Repository", "Type: Question" ] 14 | contributors: 15 | exclude: 16 | names: [ "dependabot", "dependabot[bot]" ] 17 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Kotlin Extension 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - labeled 7 | 8 | jobs: 9 | add-to-project: 10 | name: Add Dependency Upgrade PR to project 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/add-to-project@main 14 | with: 15 | project-url: https://github.com/orgs/AxonFramework/projects/2 16 | github-token: ${{ secrets.ADD_PROJECT_TOKEN }} 17 | labeled: 'Type: Dependency Upgrade' 18 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-automation.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot Automation 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot-approve: 10 | name: Dependabot PR Automation 11 | 12 | runs-on: ubuntu-latest 13 | if: ${{ github.actor == 'dependabot[bot]' }} 14 | steps: 15 | - name: Retrieve Dependabot metadata 16 | id: metadata 17 | uses: dependabot/fetch-metadata@v2 18 | with: 19 | github-token: "${{ secrets.GITHUB_TOKEN }}" 20 | 21 | - name: Approve Pull Request 22 | run: gh pr review --approve "$PR_URL" 23 | env: 24 | PR_URL: ${{github.event.pull_request.html_url}} 25 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 26 | 27 | dependabot-auto-merge: 28 | runs-on: ubuntu-latest 29 | if: ${{ github.actor == 'dependabot[bot]' }} 30 | steps: 31 | - name: Retrieve Dependabot metadata 32 | id: metadata 33 | uses: dependabot/fetch-metadata@v2 34 | with: 35 | github-token: "${{ secrets.GITHUB_TOKEN }}" 36 | 37 | - name: Auto-merge Pull Request 38 | if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} 39 | run: gh pr merge --auto --merge "$PR_URL" 40 | env: 41 | PR_URL: ${{github.event.pull_request.html_url}} 42 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Trigger documentation build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths: 8 | - 'docs/**' 9 | pull_request: 10 | branches: 11 | - 'main' 12 | paths: 13 | - 'docs/**' 14 | 15 | env: 16 | VALE_VERSION: ${{ vars.LIBRARY_VALE_VERSION || '3.3.0' }} 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Install Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: '20' 29 | 30 | - name: Install vale 31 | run: | 32 | wget "https://github.com/errata-ai/vale/releases/download/v${VALE_VERSION}/vale_${VALE_VERSION}_Linux_64-bit.tar.gz" 33 | sudo tar -xvzf vale_${VALE_VERSION}_Linux_64-bit.tar.gz -C /usr/local/bin vale 34 | 35 | - name: Generate Site 36 | run: | 37 | cd docs/_playbook/ 38 | npm install 39 | export GIT_CREDENTIALS='https://axoniq-devops:${{ secrets.LIBRARY_DEVBOT_TOKEN }}@github.com' 40 | echo 'Using' `vale -v` 41 | npx antora playbook.yaml 42 | 43 | - name: Notify AxonIQ Library (if a push to a tracked branch) 44 | if: ${{ github.event_name == 'push'}} 45 | uses: actions/github-script@v7 46 | with: 47 | github-token: ${{ secrets.LIBRARY_DEVBOT_TOKEN }} 48 | script: | 49 | await github.rest.actions.createWorkflowDispatch({ 50 | owner: 'AxonIQ', 51 | repo: 'axoniq-library-site', 52 | workflow_id: 'publish.yml', 53 | ref: 'main' 54 | }) -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Kotlin Extension 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - axon-kotlin-*.*.x 9 | 10 | jobs: 11 | build: 12 | name: Test and Build on JDK ${{ matrix.java-version }} 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - java-version: 8 19 | sonar-enabled: false 20 | deploy-enabled: true 21 | - java-version: 11 22 | sonar-enabled: false 23 | deploy-enabled: false 24 | - java-version: 17 25 | sonar-enabled: true 26 | deploy-enabled: false 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Set up JDK ${{ matrix.java-version }} 33 | uses: actions/setup-java@v4.7.1 34 | with: 35 | distribution: 'zulu' 36 | java-version: ${{ matrix.java-version }} 37 | cache: "maven" 38 | server-id: central 39 | server-username: MAVEN_USERNAME 40 | server-password: MAVEN_PASSWORD 41 | 42 | - name: Regular Build 43 | if: ${{ !matrix.sonar-enabled }} 44 | run: | 45 | ./mvnw -B -U -Dstyle.color=always clean verify 46 | 47 | - name: Build with Coverage reports 48 | if: matrix.sonar-enabled 49 | run: | 50 | ./mvnw -B -U -Dstyle.color=always -Dcoverage clean verify 51 | 52 | - name: Sonar Analysis 53 | if: matrix.sonar-enabled 54 | run: | 55 | ./mvnw -B -Dstyle.color=always sonar:sonar \ 56 | -Dsonar.projectKey=AxonFramework_extension-kotlin \ 57 | -Dsonar.organization=axonframework \ 58 | -Dsonar.host.url=https://sonarcloud.io \ 59 | -Dsonar.login=${{ secrets.SONAR_TOKEN }} 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | 63 | - name: Deploy to Sonatype 64 | if: success() && matrix.deploy-enabled 65 | run: | 66 | ./mvnw -B -U -Dstyle.color=always deploy -DskipTests=true 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | MAVEN_USERNAME: ${{ secrets.SONATYPE_TOKEN_ID }} 70 | MAVEN_PASSWORD: ${{ secrets.SONATYPE_TOKEN_PASS }} 71 | 72 | - name: Notify success to Slack 73 | if: success() 74 | env: 75 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 76 | uses: voxmedia/github-action-slack-notify-build@v2 77 | with: 78 | channel_id: CAGSEC92A 79 | status: SUCCESS 80 | color: good 81 | 82 | - name: Notify failure to Slack 83 | if: failure() 84 | env: 85 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 86 | uses: voxmedia/github-action-slack-notify-build@v2 87 | with: 88 | channel_id: CAGSEC92A 89 | status: FAILED 90 | color: danger 91 | -------------------------------------------------------------------------------- /.github/workflows/pullrequest.yml: -------------------------------------------------------------------------------- 1 | name: Kotlin Extension 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | name: Test and Build on JDK ${{ matrix.java-version }} 9 | runs-on: ubuntu-latest 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - java-version: 8 15 | sonar-enabled: false 16 | - java-version: 11 17 | sonar-enabled: false 18 | - java-version: 17 19 | sonar-enabled: true 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up JDK ${{ matrix.java-version }} 26 | uses: actions/setup-java@v4.7.1 27 | with: 28 | distribution: 'zulu' 29 | java-version: ${{ matrix.java-version }} 30 | cache: "maven" 31 | server-id: central 32 | server-username: MAVEN_USERNAME 33 | server-password: MAVEN_PASSWORD 34 | 35 | - name: Regular Build 36 | if: ${{ !matrix.sonar-enabled }} 37 | run: | 38 | ./mvnw -B -U -Dstyle.color=always clean verify 39 | 40 | - name: Build with Coverage reports 41 | if: matrix.sonar-enabled 42 | run: | 43 | ./mvnw -B -U -Dstyle.color=always -Dcoverage clean verify 44 | 45 | - name: Sonar Analysis 46 | if: ${{ success() && matrix.sonar-enabled && github.event.pull_request.head.repo.full_name == github.repository }} 47 | run: | 48 | ./mvnw -B -Dstyle.color=always sonar:sonar \ 49 | -Dsonar.projectKey=AxonFramework_extension-kotlin \ 50 | -Dsonar.organization=axonframework \ 51 | -Dsonar.host.url=https://sonarcloud.io \ 52 | -Dsonar.login=${{ secrets.SONAR_TOKEN }} 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/release-notes.yml: -------------------------------------------------------------------------------- 1 | # Trigger the workflow on milestone events 2 | on: 3 | milestone: 4 | types: [closed] 5 | name: Milestone Closure 6 | jobs: 7 | create-release-notes: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout code 11 | uses: actions/checkout@v4 12 | - name: Create Release Notes Markdown 13 | uses: docker://decathlon/release-notes-generator-action:3.1.5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 16 | OUTPUT_FOLDER: temp_release_notes 17 | USE_MILESTONE_TITLE: "true" 18 | - name: Get the name of the created Release Notes file and extract Version 19 | run: | 20 | RELEASE_NOTES_FILE=$(ls temp_release_notes/*.md | head -n 1) 21 | echo "RELEASE_NOTES_FILE=$RELEASE_NOTES_FILE" >> $GITHUB_ENV 22 | VERSION=$(echo ${{ github.event.milestone.title }} | cut -d' ' -f2) 23 | echo "VERSION=$VERSION" >> $GITHUB_ENV 24 | - name: Create a Draft Release Notes on GitHub 25 | id: create_release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token 29 | with: 30 | tag_name: axon-kotlin-${{ env.VERSION }} 31 | release_name: Axon Kotlin Extension v${{ env.VERSION }} 32 | body_path: ${{ env.RELEASE_NOTES_FILE }} 33 | draft: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Maven build artifacts can be ignored 2 | target/ 3 | # ...but keep the wrapper jar 4 | !.mvn/wrapper/maven-wrapper.jar 5 | 6 | *.iml 7 | axon.ipr 8 | axon.iws 9 | .idea/ 10 | **/*.iml 11 | .classpath 12 | .project 13 | .settings/ 14 | events/ 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AxonFramework/extension-kotlin/2e69b0fa918fe6460606df32269956584871fa0f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010-2024. Axon Framework 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 17 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Thank you for your interest in contributing to the Axon Framework Kotlin Extension. To make sure using Axon is a smooth 4 | experience for everybody, we've set up a number of guidelines to follow. 5 | 6 | There are different ways in which you can contribute to the framework: 7 | 8 | 1. You can report any bugs, feature requests or ideas about improvements on 9 | our [issue page](https://github.com/AxonFramework/extension-kotlin/issues/new/choose). All ideas are welcome. Please 10 | be as exact as possible when reporting bugs. This will help us reproduce and thus solve the problem faster. 11 | 2. If you have created a component for your own application that you think might be useful to include in the framework, 12 | send us a pull request (or a patch / zip containing the source code). We will evaluate it and try to fit it in the 13 | framework. Please make sure code is properly documented using JavaDoc. This helps us to understand what is going on. 14 | 3. If you know of any other way you think you can help us, do not hesitate to send a message to 15 | the [AxonIQ's discussion platform](https://discuss.axoniq.io/). 16 | 17 | ## Code Contributions 18 | 19 | If you're contributing code, please take care of the following: 20 | 21 | ### Contributor Licence Agreement 22 | 23 | To keep everyone out of trouble (both you and us), we require that all contributors (digitally) sign a Contributor 24 | License Agreement. Basically, the agreement says that we may freely use the code you contribute to the Axon Framework 25 | Kotlin Extension, and that we won't hold you liable for any unfortunate side effects that the code may cause. 26 | 27 | To sign the CLA, visit: https://cla-assistant.io/AxonFramework/extension-kotlin 28 | 29 | ### Code Style 30 | 31 | We're trying very hard to maintain a consistent style of coding throughout the code base. Think of things like indenting 32 | using 4 spaces, putting opening brackets (the '{') on the same line and putting proper JavaDoc on all non-private 33 | members. 34 | 35 | If you're using IntelliJ IDEA, you can download the code style 36 | definition [here](https://github.com/AxonFramework/AxonFramework/blob/master/axon_code_style.xml). Simply import the XML 37 | file in under "Settings -> Code Style -> Scheme -> Import Scheme". Doing so should make the code style selectable 38 | immediately. 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2019] [AxonIQ] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axon Framework - Kotlin Extension 2 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.axonframework.extensions.kotlin/axon-kotlin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.axonframework.extensions.kotlin/axon-kotlin) 3 | ![Build Status](https://github.com/AxonFramework/extension-kotlin/workflows/Kotlin%20Extension/badge.svg?branch=master) 4 | [![SonarCloud Status](https://sonarcloud.io/api/project_badges/measure?project=AxonFramework_extension-kotlin&metric=alert_status)](https://sonarcloud.io/dashboard?id=AxonFramework_extension-kotlin) 5 | [![Open Source Helpers](https://www.codetriage.com/axonframework/extension-kotlin/badges/users.svg)](https://www.codetriage.com/axonframework/extension-kotlin) 6 | 7 | _Note:_ This extension is still in an experimental stage. 8 | 9 | Axon Framework is a framework for building evolutionary, event-driven microservice systems, 10 | based on the principles of Domain Driven Design, Command-Query Responsibility Segregation (CQRS) and Event Sourcing. 11 | 12 | As such it provides you the necessary building blocks to follow these principles. 13 | Building blocks like Aggregate factories and Repositories, Command, Event and Query Buses and an Event Store. 14 | The framework provides sensible defaults for all of these components out of the box. 15 | 16 | This set up helps you create a well-structured application without having to bother with the infrastructure. 17 | The main focus can thus become your business functionality. 18 | 19 | This repository provides an extension to the Axon Framework: Kotlin. It provides functionality to leverage Kotlin features to be used with Axon Framework. 20 | 21 | For more information on anything Axon, please visit our website, [http://axoniq.io](http://axoniq.io). 22 | 23 | ## Getting started 24 | 25 | ### Dependencies 26 | 27 | For the Kotlin extension itself you can get the version from the [axon-bom](https://github.com/AxonFramework/axon-bom) or use the following coordinates: 28 | 29 | **Maven** 30 | 31 | ``` 32 | 33 | org.axonframework.extensions.kotlin 34 | axon-kotlin 35 | 4.6.0 36 | 37 | ``` 38 | 39 | **Gradle** 40 | 41 | ``` 42 | implementation("org.axonframework.extensions.kotlin:axon-kotlin:4.6.0") 43 | ``` 44 | 45 | For the Kotlin testing extension itself please use the following coordinates: 46 | 47 | **Maven** 48 | 49 | ``` 50 | 51 | org.axonframework.extensions.kotlin 52 | axon-kotlin-test 53 | 4.6.0 54 | 55 | ``` 56 | 57 | **Gradle** 58 | 59 | ``` 60 | implementation("org.axonframework.extensions.kotlin:axon-kotlin-test:4.6.0") 61 | ``` 62 | 63 | 64 | ## Receiving help 65 | 66 | Are you having trouble using the extension? 67 | We'd like to help you out the best we can! 68 | There are a couple of things to consider when you're traversing anything Axon: 69 | 70 | * Checking the [documentation](https://docs.axoniq.io/home/) should be your first stop, 71 | as the majority of possible scenarios you might encounter when using Axon should be covered there. 72 | * If the Reference Guide does not cover a specific topic you would've expected, 73 | we'd appreciate if you could post a [new thread/topic on our library fourms describing the problem](https://discuss.axoniq.io/c/26). 74 | * There is a [forum](https://discuss.axoniq.io/) to support you in the case the reference guide did not sufficiently answer your question. 75 | Axon Framework and Server developers will help out on a best effort basis. 76 | Know that any support from contributors on posted question is very much appreciated on the forum. 77 | * Next to the forum we also monitor Stack Overflow for any questions which are tagged with `axon`. 78 | 79 | ## Feature requests and issue reporting 80 | 81 | We use GitHub's [issue tracking system](https://github.com/AxonFramework/extension-kotlin/issues) for new feature 82 | request, extension enhancements and bugs. 83 | Prior to filing an issue, please verify that it's not already reported by someone else. 84 | 85 | When filing bugs: 86 | * A description of your setup and what's happening helps us figuring out what the issue might be 87 | * Do not forget to provide the version you're using 88 | * If possible, share a stack trace, using the Markdown semantic ``` 89 | 90 | When filing features: 91 | * A description of the envisioned addition or enhancement should be provided 92 | * (Pseudo-)Code snippets showing what it might look like help us understand your suggestion better 93 | * If you have any thoughts on where to plug this into the framework, that would be very helpful too 94 | * Lastly, we value contributions to the framework highly. So please provide a Pull Request as well! 95 | 96 | ## Building the extension 97 | 98 | If you want to build the extension locally, you need to check it out from GiHub and run the following command: 99 | 100 | ./mvnw clean install 101 | 102 | ### Producing JavaDocs and Sources archive 103 | 104 | Please execute the following command line if you are interested in producing KDoc and Source archives: 105 | 106 | ./mvnw clean install -Pjavadoc-and-sources 107 | 108 | 109 | --- 110 | -------------------------------------------------------------------------------- /coverage-report/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 4.0.0 21 | 22 | org.axonframework.extensions.kotlin 23 | axon-kotlin-parent 24 | 4.11.2-SNAPSHOT 25 | 26 | 27 | axon-coverage-report 28 | 29 | Axon Framework Kotlin Extension - Coverage Report 30 | Project depending on all modules to construct a combined coverage report 31 | 32 | 33 | 34 | org.axonframework.extensions.kotlin 35 | axon-kotlin 36 | ${project.version} 37 | runtime 38 | 39 | 40 | org.axonframework.extensions.kotlin 41 | axon-kotlin-test 42 | ${project.version} 43 | runtime 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.jacoco 51 | jacoco-maven-plugin 52 | 53 | 54 | report-aggregate 55 | verify 56 | 57 | report-aggregate 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation For Axon Framework - Kotlin Extension. 2 | 3 | This folder contains the docs related to the Kotlin Extension for Axon Framework. The docs in this folder are written as part of the [AxonIQ Library](https://library.axoniq.io), and are [written in Ascii and built with Antora.](https://library.axoniq.io/contibution-guide/overview/platform.html) 4 | 5 | The following are the current documentation sources (folders): 6 | 7 | - `extension-guide` : [The Kotlin Extension Guide](https://library.axoniq.io/kotlin-extension-reference/index.html) 8 | 9 | ## Contributing to the docs. 10 | 11 | You are welcome to contribute to these docs. Whether you want to fix a typo, or you find something missing, something that is not clear or can be improved, or even if you want to write an entire piece of docs to illustrate something that could help others to understand the use of the Bike Rental App, you are more than welcome to send a Pull Request to this GitHub repository. Just make sure you follow the guidelines explained in [AxonIQ Library Contribution Guide](https://library.axoniq.io/contibution-guide/index.html) 12 | 13 | ## Building and testing these docs locally. 14 | 15 | If you want to build and explore the docs locally (because you have made changes or before contributing), you can use the Antora's build file in `docs/_playbook` folder. 16 | 17 | You can check the [detailed information on how the process to build the docs works](https://library.axoniq.io/contibution-guide/overview/build.html), but in short, all you have to do is: 18 | 19 | 1. Make sure you have Node (a LTS version is preferred), Antora and Vale installed in your system. 20 | 2. CD to the `docs/_playbook` folder. 21 | 3. Run `npx antora playbook.yaml`. Antora will generate the set of static html files under `docs/_playbook/build/site` 22 | 4. Move to `docs/_playbook/build/site` and execute some local http server to serve files in that directory. For example by executing `python3 -m http.server 8070` 23 | 5. Open your browser and go to `http://localhost:8070`. You should be able to navigate the local version of the docs. 24 | -------------------------------------------------------------------------------- /docs/_playbook/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | .vscode 4 | vale 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /docs/_playbook/.vale.ini: -------------------------------------------------------------------------------- 1 | StylesPath = vale 2 | 3 | MinAlertLevel = suggestion 4 | 5 | Packages = http://github.com/AxonIQ/axoniq-vale-package/releases/latest/download/axoniq-vale-package.zip 6 | 7 | Vocab = general, AxonIQ, Java, Names_Terms, misc 8 | 9 | [*.{adoc,html}] 10 | BasedOnStyles = AxonIQ, proselint, Google 11 | 12 | Google.Headings = NO # Diasable in favor od AxonIQ one 13 | Google.Parens = NO # Disable warning about using parens 14 | Google.Quotes = NO # Diasable "commas and periods go inside quotation marks" 15 | Google.WordList = NO # Disable Google's word list 16 | Google.Passive = NO # Allow the use of Passive voice 17 | Google.Colons = NO # Allow the use of Colons 18 | Google.Will = NO # Allow use will 19 | Google.Contractions = NO 20 | Google.We = NO 21 | 22 | 23 | AxonIQ.AcronymCase = NO 24 | AxonIQ.HeadingTitle = NO 25 | -------------------------------------------------------------------------------- /docs/_playbook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@antora/atlas-extension": "^1.0.0-alpha.2", 4 | "@antora/cli": "^3.2.0-alpha.2", 5 | "@antora/lunr-extension": "^1.0.0-alpha.8", 6 | "@antora/site-generator": "^3.2.0-alpha.2", 7 | "@asciidoctor/tabs": "^1.0.0-beta.6", 8 | "@axoniq/antora-vale-extension": "^0.1.1", 9 | "asciidoctor-kroki": "^0.17.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/_playbook/playbook.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: Kotlin Extension docs PREVIEW 3 | start_page: kotlin-extension-reference::index.adoc 4 | 5 | content: 6 | sources: 7 | - url: ../.. 8 | start_paths: ['docs/*', '!docs/_*'] 9 | 10 | asciidoc: 11 | attributes: 12 | experimental: true 13 | page-pagination: true 14 | kroki-fetch-diagram: true 15 | # primary-site-manifest-url: https://library.axoniq.io/site-manifest.json 16 | extensions: 17 | - asciidoctor-kroki 18 | - '@asciidoctor/tabs' 19 | 20 | antora: 21 | extensions: 22 | - id: prose-linting 23 | require: '@axoniq/antora-vale-extension' 24 | enabled: true 25 | vale_config: .vale.ini 26 | update_styles: true 27 | - id: lunr 28 | require: '@antora/lunr-extension' 29 | enabled: true 30 | index_latest_only: true 31 | - id: atlas 32 | require: '@antora/atlas-extension' 33 | 34 | runtime: 35 | fetch: true # fetch remote repos 36 | log: 37 | level: info 38 | failure_level: error 39 | 40 | ui: 41 | bundle: 42 | url: https://github.com/AxonIQ/axoniq-library-ui/releases/download/v.0.1.10/ui-bundle.zip 43 | -------------------------------------------------------------------------------- /docs/reference/antora.yml: -------------------------------------------------------------------------------- 1 | name: kotlin-extension-reference 2 | title: Kotlin Extension Reference 3 | version: 4 | axon-kotlin-(?+({0..9}).+({0..9})).*: $ 5 | master: development 6 | prerelease: true 7 | start_page: ROOT:index.adoc 8 | 9 | asciidoc: 10 | attributes: 11 | component_description: Extension enhancing the development experience when using Kotlin 12 | type: extension-reference 13 | group: axon-framework 14 | 15 | nav: 16 | - modules/nav.adoc -------------------------------------------------------------------------------- /docs/reference/modules/ROOT/pages/commands.adoc: -------------------------------------------------------------------------------- 1 | :navtitle: Commands 2 | = Commands 3 | 4 | This section describes the additional functionality attached to Axon's xref:axon-framework-reference:axon-framework-commands:index.adoc[command dispatching and handling] logic. 5 | 6 | [[commandgateway]] 7 | == `CommandGateway` 8 | 9 | An inlined method has been introduced on the `CommandGateway` which allows the introduction of a dedicated function to be invoked upon success or failure of handling the command. As such it provides a shorthand instead of using the `CommandCallback` directly yourself. 10 | 11 | Here is a sample of how this can be utilized within your own project: 12 | 13 | [source,kotlin] 14 | ---- 15 | import org.axonframework.commandhandling.CommandMessage 16 | import org.axonframework.commandhandling.gateway.CommandGateway 17 | import org.axonframework.messaging.MetaData 18 | import org.slf4j.LoggerFactory 19 | 20 | class CommandDispatcher(private val commandGateway: CommandGateway) { 21 | 22 | private val logger = LoggerFactory.getLogger(CommandDispatcher::class.java) 23 | 24 | // Sample usage providing specific logging logic, next to for example the LoggingInterceptor 25 | fun issueCardCommand() { 26 | commandGateway.send( 27 | command = IssueCardCommand(), 28 | onSuccess = { message: CommandMessage, result: Any, _: MetaData -> 29 | logger.info("Successfully handled [{}], resulting in [{}]", message, result) 30 | }, 31 | onError = { result: Any, exception: Throwable, _: MetaData -> 32 | logger.warn( 33 | "Failed handling the IssueCardCommand, with output [{} and exception [{}]", 34 | result, exception 35 | ) 36 | } 37 | ) 38 | } 39 | } 40 | 41 | class IssueCardCommand 42 | ---- 43 | -------------------------------------------------------------------------------- /docs/reference/modules/ROOT/pages/events.adoc: -------------------------------------------------------------------------------- 1 | :navtitle: Events 2 | = Events 3 | 4 | This section describes the additional functionality attached to Axon's xref:axon-framework-reference:events:index.adoc[event publication and handling] logic. 5 | 6 | == Event upcasters 7 | 8 | A simplified implementation of the xref:axon-framework-reference:events:event-versioning.adoc#event-upcasting[Single Event Upcaster] is given, which allows for a shorter implementation cycle. Making an upcaster to upcast the `CardIssuedEvent` from revision `0` to `1` can be written as follows: 9 | 10 | [source,kotlin] 11 | ---- 12 | import com.fasterxml.jackson.databind.JsonNode 13 | import org.axonframework.serialization.upcasting.event.SingleEventUpcaster 14 | 15 | fun `CardIssuedEvent 0 to 1 Upcaster`(): SingleEventUpcaster = 16 | EventUpcaster.singleEventUpcaster( 17 | eventType = CardIssuedEvent::class, 18 | storageType = JsonNode::class, 19 | revisions = Revisions("0", "1") 20 | ) { event -> 21 | // Perform your upcasting process of the CardIssuedEvent here 22 | event 23 | } 24 | 25 | class CardIssuedEvent 26 | ---- 27 | 28 | Alternatively, since `Revisions` is essentially a `Pair` of `String`, it is also possible to use link:https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/to.html[Kotlin's `to` function,window=_blank,role=external]: 29 | 30 | [source,kotlin] 31 | ---- 32 | EventUpcaster.singleEventUpcaster( 33 | eventType = CardIssuedEvent::class, 34 | storageType = JsonNode::class, 35 | revisions = "0" to "1" 36 | ) { event -> 37 | // Perform your upcasting process of the CardIssuedEvent here 38 | event 39 | } 40 | ---- 41 | -------------------------------------------------------------------------------- /docs/reference/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | :navtitle: Kotlin Extension 2 | = Kotlin 3 | 4 | link:https://kotlinlang.org/[Kotlin,window=_blank,role=external] is a programming language which interoperates fully with Java and the JVM. As Axon is written in Java it can be used in conjunction with Kotlin too, offering a different feel when using the framework. 5 | 6 | Some of Axon's API's work perfectly well in Java, but have a rather awkward feel when transitioning over to Kotlin. The goal of the link:https://github.com/AxonFramework/extension-kotlin[Kotlin Extension,window=_blank,role=external] is to remove that awkwardness, by providing link:https://kotlinlang.org/docs/reference/inline-functions.html[inline and reified,window=_blank,role=external] methods of Axon's API. 7 | 8 | Several solutions are currently given, which can roughly be segregated into the distinct types of messages used by Axon. This thus provides a xref:commands.adoc[], xref:events.adoc[] and xref:queries.adoc[] section on this guide. 9 | 10 | [NOTE] 11 | .Experimental Release 12 | ==== 13 | Currently, the link:https://github.com/AxonFramework/extension-kotlin[Kotlin Extension,window=_blank,role=external] has been release experimentally (for example, release 0.1.0). This means that all implementations are subject to change until a full release (for example, a release 1.0.0) has been made. 14 | ==== 15 | -------------------------------------------------------------------------------- /docs/reference/modules/ROOT/pages/queries.adoc: -------------------------------------------------------------------------------- 1 | :navtitle: Queries 2 | = Queries 3 | 4 | This section describes the additional functionality attached to Axon's xref:axon-framework-reference:queries:index.adoc[query dispatching and handling] logic. 5 | 6 | [[querygateway]] 7 | == `QueryGateway` 8 | 9 | Several inlined methods have been introduced on the `QueryGateway` to use generics instead of explicit `Class` objects and `ResponseType` parameters. 10 | 11 | [source,kotlin] 12 | ---- 13 | import org.axonframework.queryhandling.QueryGateway 14 | 15 | class QueryDispatcher(private val queryGateway: QueryGateway) { 16 | fun getTotalNumberOfCards(): Int { 17 | val query = CountCardSummariesQuery() 18 | // Query will return a CompletableFuture so it has to be handled 19 | return queryGateway.query(query) 20 | .join() 21 | } 22 | } 23 | 24 | data class CountCardSummariesQuery(val filter: String = "") 25 | ---- 26 | 27 | In some cases, Kotlin's type inference system can deduce types without explicit generic parameters. One example of this would be an explicit return parameter: 28 | 29 | [source,kotlin] 30 | ---- 31 | import org.axonframework.queryhandling.QueryGateway 32 | import java.util.concurrent.CompletableFuture 33 | 34 | class QueryDispatcher(private val queryGateway: QueryGateway) { 35 | fun getTotalNumberOfCards(): CompletableFuture = 36 | queryGateway.query(CountCardSummariesQuery()) 37 | } 38 | 39 | data class CountCardSummariesQuery(val filter: String = "") 40 | ---- 41 | 42 | There are multiple variants of the `query` method provided, for each type of `ResponseType`: 43 | 44 | - `query` 45 | - `queryOptional` 46 | - `queryMany` 47 | 48 | [[queryupdateemitter]] 49 | == `QueryUpdateEmitter` 50 | 51 | An inline `emit` method has been added to `QueryUpdateEmitter` to simplify emit method's call by using generics and moving the lambda predicate at the end of parameter list. This way the lambda function can be moved outside of the parentheses. 52 | 53 | [source,kotlin] 54 | ---- 55 | import org.axonframework.queryhandling.QueryUpdateEmitter 56 | import org.axonframework.eventhandling.EventHandler 57 | 58 | class CardSummaryProjection (private val queryUpdateEmitter : QueryUpdateEmitter) { 59 | @EventHandler 60 | fun on(event : CardIssuedEvent) { 61 | // Update projection here 62 | 63 | // Then emit the CountChangedUpdate to subscribers of CountCardSummariesQuery 64 | // with the given filter 65 | queryUpdateEmitter 66 | .emit(CountChangedUpdate()) { query -> 67 | // Sample filter based on ID field 68 | event.id.startsWith(query.idFilter) 69 | } 70 | } 71 | } 72 | 73 | class CardIssuedEvent(val id : String) 74 | class CountChangedUpdate 75 | data class CountCardSummariesQuery(val idFilter: String = "") 76 | ---- 77 | -------------------------------------------------------------------------------- /docs/reference/modules/ROOT/pages/release-notes.adoc: -------------------------------------------------------------------------------- 1 | = Release Notes Kotlin Extension 2 | :navtitle: Release notes 3 | 4 | You can find the release notes for the Kotlin Extension of version 4.10.0 below. 5 | For earlier releases, please go to the link:https://legacydocs.axoniq.io/reference-guide/release-notes/rn-extensions/rn-kotlin[legacy documentation]. 6 | 7 | == Release 4.11.0 8 | 9 | === _Enhancements_ 10 | 11 | - [#368] `AxonSerializer` documentation and enforce `ReplayToken.context` to String link:https://github.com/AxonFramework/extension-kotlin/pull/370[#370] 12 | - `AxonSerializer` documentation and enforce `ReplayToken#context` to String link:https://github.com/AxonFramework/extension-kotlin/issues/368[#368] 13 | 14 | === _Contributors_ 15 | 16 | We'd like to thank all the contributors who worked on this release! 17 | 18 | - link:https://github.com/MateuszNaKodach[@MateuszNaKodach] 19 | 20 | == Release 4.10.0 21 | 22 | . Upgrades the Kotlin Extension to be compatible with Axon Framework 4.10.0 23 | . Adds a serializer with `kotlin.serialization` support 24 | 25 | See the link:https://github.com/AxonFramework/extension-kotlin/releases/tag/axon-kotlin-4.10.0[GitHub release notes] for an exhaustive list of all changes. -------------------------------------------------------------------------------- /docs/reference/modules/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:ROOT:commands.adoc[] 2 | * xref:ROOT:events.adoc[] 3 | * xref:ROOT:queries.adoc[] 4 | * xref:ROOT:release-notes.adoc[] -------------------------------------------------------------------------------- /kotlin-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | Axon Framework - Kotlin Extension - Test 22 | Module for the Kotlin Test Extension of Axon Framework 23 | 24 | 25 | axon-kotlin-parent 26 | org.axonframework.extensions.kotlin 27 | 4.11.2-SNAPSHOT 28 | 29 | 30 | axon-kotlin-test 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | org.axonframework 39 | axon-test 40 | provided 41 | 42 | 43 | org.hamcrest 44 | hamcrest-core 45 | 3.0 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.jetbrains.kotlin 54 | kotlin-maven-plugin 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /kotlin-test/src/main/kotlin/org/axonframework/extension/kotlin/test/FixtureExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extension.kotlin.test 18 | 19 | import org.axonframework.test.aggregate.AggregateTestFixture 20 | import org.axonframework.test.aggregate.FixtureConfiguration 21 | import org.axonframework.test.aggregate.ResultValidator 22 | import org.axonframework.test.aggregate.TestExecutor 23 | import org.axonframework.test.saga.SagaTestFixture 24 | import kotlin.reflect.KClass 25 | 26 | /** 27 | * Creates an aggregate test fixture for aggregate [T]. 28 | * @param T reified type of the aggregate. 29 | * @return aggregate test fixture. 30 | * @since 0.2.0 31 | */ 32 | inline fun aggregateTestFixture() = 33 | AggregateTestFixture(T::class.java) 34 | 35 | /** 36 | * Alias for the `when` method to avoid name clash with Kotlin's `when`. 37 | * @param command command to pass to the when method. 38 | * @param T aggregate type. 39 | * @return result validator. 40 | * @since 0.2.0 41 | */ 42 | fun TestExecutor.whenever(command: Any): ResultValidator = this.`when`(command) 43 | 44 | /** 45 | * Alias for the `when` method to avoid name clash with Kotlin's `when`. 46 | * @param command command to pass to the when method. 47 | * @param metaData metadata map. 48 | * @param T aggregate type. 49 | * @return result validator. 50 | * @since 0.2.0 51 | */ 52 | fun TestExecutor.whenever(command: Any, metaData: Map): ResultValidator = this.`when`(command, metaData) 53 | 54 | /** 55 | * Registers subtypes of this aggregate to support aggregate polymorphism. Command Handlers defined on this subtype 56 | * will be considered part of this aggregate's handlers. 57 | * 58 | * @param subtypes subtypes in this polymorphic hierarchy 59 | * @return the current FixtureConfiguration, for fluent interfacing 60 | * @since 0.2.0 61 | */ 62 | fun AggregateTestFixture.withSubtypes(vararg subtypes: KClass) = 63 | this.withSubtypes(* subtypes.map { it.java }.toTypedArray()) 64 | 65 | /** 66 | * Indicates that a field with given {@code fieldName}, which is declared in given {@code declaringClass} 67 | * is ignored when performing deep equality checks. 68 | * 69 | * @param T type of fixture target. 70 | * @param F filed type. 71 | * @param fieldName The name of the field 72 | * @return the current FixtureConfiguration, for fluent interfacing 73 | * @since 0.2.0 74 | */ 75 | inline fun FixtureConfiguration.registerIgnoredField(fieldName: String): FixtureConfiguration = 76 | this.registerIgnoredField(F::class.java, fieldName) 77 | 78 | /** 79 | * Creates a saga test fixture for saga [T]. 80 | * @param T reified type of the saga. 81 | * @return saga test fixture. 82 | * @since 0.2.0 83 | */ 84 | inline fun sagaTestFixture() = 85 | SagaTestFixture(T::class.java) 86 | 87 | /** 88 | * Reified version of command gateway registration. 89 | * @param T saga type 90 | * @param I command gateway type. 91 | * @return registered command gateway instance. 92 | * @since 0.2.0 93 | */ 94 | inline fun SagaTestFixture.registerCommandGateway(): I = 95 | this.registerCommandGateway(I::class.java) 96 | 97 | /** 98 | * Reified version of command gateway registration. 99 | * @param T saga type 100 | * @param I command gateway type. 101 | * @param stubImplementation stub implementation. 102 | * @return registered command gateway instance. 103 | * @since 0.2.0 104 | */ 105 | inline fun SagaTestFixture.registerCommandGateway(stubImplementation: I): I = 106 | this.registerCommandGateway(I::class.java, stubImplementation) 107 | 108 | /** 109 | * Expect a KClass exception for aggregate [T]. 110 | * @param T aggregate type 111 | * @param E exception type 112 | * @param expectedException kotlin class of the exception 113 | * @return this 114 | * @since 0.2.0 115 | */ 116 | fun > ResultValidator.expectException(expectedException: E): ResultValidator = 117 | this.expectException(expectedException.java) -------------------------------------------------------------------------------- /kotlin-test/src/test/kotlin/org/axonframework/extension/kotlin/test/FixtureExtensionsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2023. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extension.kotlin.test 18 | 19 | import kotlin.test.Test 20 | 21 | internal class FixtureExtensionsTest { 22 | 23 | @Test 24 | fun `Aggregate test fixture extension should create an aggregate fixture`() { 25 | aggregateTestFixture() 26 | } 27 | 28 | @Test 29 | fun `Saga test fixture extension should create a saga fixture`() { 30 | sagaTestFixture() 31 | } 32 | 33 | @Test 34 | fun `Whenever extension should apply to an aggregate fixture`() { 35 | val fixture = aggregateTestFixture() 36 | 37 | fixture 38 | // Call on an AggregateTestFixture instance 39 | .whenever(ExampleCommand("id")) 40 | .expectNoEvents() 41 | 42 | fixture 43 | // Call on an AggregateTestFixture instance 44 | .whenever(ExampleCommand("id"), mapOf()) 45 | .expectNoEvents() 46 | } 47 | 48 | @Test 49 | fun `Whenever extension should apply to a result validator`() { 50 | val fixture = aggregateTestFixture() 51 | 52 | fixture 53 | .givenNoPriorActivity() 54 | // Call on a ResultValidator instance 55 | .whenever(ExampleCommand("id")) 56 | .expectNoEvents() 57 | 58 | fixture 59 | .givenNoPriorActivity() 60 | // Call on a ResultValidator instance 61 | .whenever(ExampleCommand("id"), mapOf()) 62 | .expectNoEvents() 63 | } 64 | 65 | @Test 66 | fun `Expect exception extension should accept a kotlin class`() { 67 | val fixture = aggregateTestFixture() 68 | fixture 69 | .whenever(ExampleCommandWithException("id")) 70 | .expectException(Exception::class) 71 | } 72 | } -------------------------------------------------------------------------------- /kotlin-test/src/test/kotlin/org/axonframework/extension/kotlin/test/testObjects.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2023. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extension.kotlin.test 18 | 19 | import org.axonframework.commandhandling.CommandHandler 20 | import org.axonframework.commandhandling.RoutingKey 21 | import org.axonframework.modelling.command.AggregateIdentifier 22 | 23 | internal data class ExampleCommand(@RoutingKey val aggregateId: String) 24 | internal data class ExampleCommandWithException(@RoutingKey val aggregateId: String) 25 | 26 | internal class ExampleAggregate { 27 | 28 | @AggregateIdentifier 29 | lateinit var aggregateId: String 30 | 31 | constructor() 32 | 33 | @CommandHandler 34 | constructor(command: ExampleCommand) 35 | 36 | @CommandHandler 37 | constructor(command: ExampleCommandWithException) { 38 | throw Exception() 39 | } 40 | } 41 | 42 | class ExampleSaga -------------------------------------------------------------------------------- /kotlin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | Axon Framework - Kotlin Extension 22 | Module for the Kotlin Extension of Axon Framework 23 | 24 | 25 | org.axonframework.extensions.kotlin 26 | axon-kotlin-parent 27 | 4.11.2-SNAPSHOT 28 | 29 | 30 | axon-kotlin 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | org.axonframework 39 | axon-configuration 40 | provided 41 | 42 | 43 | io.projectreactor 44 | reactor-core 45 | 3.7.6 46 | test 47 | 48 | 49 | 50 | 51 | 52 | 53 | src/main/resources 54 | true 55 | 56 | 57 | 58 | 59 | org.jetbrains.kotlin 60 | kotlin-maven-plugin 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/AggregateLifecycleExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.messaging.MetaData 20 | import org.axonframework.modelling.command.Aggregate 21 | import org.axonframework.modelling.command.AggregateLifecycle 22 | import org.axonframework.modelling.command.ApplyMore 23 | 24 | /** 25 | * Alias for [AggregateLifecycle.apply] method. 26 | * @param payload payload of the event message to be applied. 27 | * @return fluent instance to apply more. 28 | * @since 0.2.0 29 | */ 30 | fun applyEvent(payload: Any): ApplyMore = AggregateLifecycle.apply(payload) 31 | 32 | /** 33 | * Alias for [AggregateLifecycle.apply] method. 34 | * @param payload payload of the event message to be applied. 35 | * @param metaData metadata to be included into the event message. 36 | * @return fluent instance to apply more. 37 | * @since 0.2.0 38 | */ 39 | fun applyEvent(payload: Any, metaData: MetaData): ApplyMore = AggregateLifecycle.apply(payload, metaData) 40 | 41 | /** 42 | * Create new aggregate instance. 43 | * @param T aggregate type. 44 | * @param factoryMethod factory method. 45 | * @return new instance of an aggregate. 46 | * @since 0.2.0 47 | */ 48 | inline fun createNew(noinline factoryMethod: () -> T): Aggregate = AggregateLifecycle.createNew(T::class.java, factoryMethod) 49 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/BuilderExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import org.axonframework.eventsourcing.EventSourcingRepository 19 | import org.axonframework.modelling.command.GenericJpaRepository 20 | 21 | /** 22 | * Reified version of the static builder for event souring repository. 23 | * @param T aggregate type. 24 | * @return event sourcing repository builder for aggregate [T] 25 | * @since 0.2.0 26 | */ 27 | inline fun eventSourcingRepositoryBuilder() = EventSourcingRepository.builder(T::class.java) 28 | 29 | 30 | /** 31 | * Reified version of the static builder for JPA repository. 32 | * @param T aggregate type. 33 | * @return Generic JPA repository builder for aggregate [T] 34 | * @since 0.2.0 35 | */ 36 | inline fun genericJpaRepositoryBuilder() = GenericJpaRepository.builder(T::class.java) -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/CommandGatewayExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.commandhandling.CommandMessage 20 | import org.axonframework.commandhandling.gateway.CommandGateway 21 | import org.axonframework.messaging.MetaData 22 | 23 | /** 24 | * Callback-style [CommandGateway.send] with dedicated on-success and on-error functions 25 | * @param command The command to send 26 | * @param onError Callback to handle failed execution 27 | * @param onSuccess Callback to handle successful execution 28 | * @param R the type of result of the command handling 29 | * @param C the type of payload of the command 30 | * @see CommandGateway.send 31 | * @since 0.1.0 32 | */ 33 | inline fun CommandGateway.send( 34 | command: C, 35 | noinline onSuccess: (commandMessage: CommandMessage, result: R, metaData: MetaData) -> Unit = { _, _, _ -> }, 36 | noinline onError: (commandMessage: CommandMessage, exception: Throwable, metaData: MetaData) -> Unit = { _, _, _ -> } 37 | ): Unit = this.send(command, ResultDiscriminatorCommandCallback(onSuccess, onError)) 38 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/EventUpcaster.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.serialization.SimpleSerializedType 20 | import org.axonframework.serialization.upcasting.event.IntermediateEventRepresentation 21 | import org.axonframework.serialization.upcasting.event.SingleEventUpcaster 22 | import kotlin.reflect.KClass 23 | 24 | /** 25 | * Helpers for event upcaster. 26 | * @since 0.1.0 27 | */ 28 | object EventUpcaster { 29 | /** 30 | * Creates a singleEventUpcaster for given type and revisions and calls [IntermediateEventRepresentation.upcastPayload] using the [converter]. 31 | */ 32 | fun singleEventUpcaster(eventType: KClass<*>, 33 | storageType: KClass, 34 | revisions: Revisions, 35 | converter: (T) -> T): SingleEventUpcaster = object : SingleEventUpcaster() { 36 | 37 | override fun canUpcast(ir: IntermediateEventRepresentation): Boolean = SimpleSerializedType(eventType.qualifiedName, revisions.first) == ir.type 38 | 39 | override fun doUpcast(ir: IntermediateEventRepresentation): IntermediateEventRepresentation = 40 | ir.upcastPayload( 41 | SimpleSerializedType(eventType.qualifiedName, revisions.second), 42 | storageType.java, 43 | converter) 44 | } 45 | } 46 | 47 | /** 48 | * Tuple of oldRevision (nullable) and newRevision. 49 | */ 50 | typealias Revisions = Pair 51 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.messaging.responsetypes.ResponseTypes 20 | import org.axonframework.queryhandling.QueryGateway 21 | import org.axonframework.queryhandling.SubscriptionQueryResult 22 | import java.util.* 23 | import java.util.concurrent.CompletableFuture 24 | import java.util.concurrent.TimeUnit 25 | import java.util.stream.Stream 26 | 27 | /** 28 | * Query Gateway extensions. 29 | * 30 | * @author Henrique Sena 31 | */ 32 | 33 | /** 34 | * Reified version of [QueryGateway.query] 35 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.MultipleInstancesResponseType] 36 | * @param query Query to send 37 | * @param Q the type of payload of the query 38 | * @param R the type of result of the query 39 | * @return [CompletableFuture] wrapping the result of the query 40 | * @see QueryGateway.query 41 | * @see ResponseTypes 42 | * @since 0.1.0 43 | */ 44 | inline fun QueryGateway.queryMany(query: Q): CompletableFuture> { 45 | return this.query(query, ResponseTypes.multipleInstancesOf(R::class.java)) 46 | } 47 | 48 | /** 49 | * Reified version of [QueryGateway.query] with explicit query name 50 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.MultipleInstancesResponseType] 51 | * @param queryName Name of the query 52 | * @param query Query to send 53 | * @param Q the type of payload of the query 54 | * @param R the type of result of the query 55 | * @return [CompletableFuture] wrapping the result of the query 56 | * @see QueryGateway.query 57 | * @see ResponseTypes 58 | * @since 0.1.0 59 | */ 60 | inline fun QueryGateway.queryMany(queryName: String, query: Q): CompletableFuture> { 61 | return this.query(queryName, query, ResponseTypes.multipleInstancesOf(R::class.java)) 62 | } 63 | 64 | /** 65 | * Reified version of [QueryGateway.query] 66 | * which expects a single object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 67 | * @param query Query to send 68 | * @param Q the type of payload of the query 69 | * @param R the type of result of the query 70 | * @return [CompletableFuture] wrapping the result of the query 71 | * @see QueryGateway.query 72 | * @see ResponseTypes 73 | * @since 0.1.0 74 | */ 75 | inline fun QueryGateway.query(query: Q): CompletableFuture { 76 | return this.query(query, ResponseTypes.instanceOf(R::class.java)) 77 | } 78 | 79 | /** 80 | * Reified version of [QueryGateway.query] with explicit query name 81 | * which expects a single object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 82 | * @param queryName Name of the query 83 | * @param query Query to send 84 | * @param Q the type of payload of the query 85 | * @param R the type of result of the query 86 | * @return [CompletableFuture] wrapping the result of the query 87 | * @see QueryGateway.query 88 | * @see ResponseTypes 89 | * @since 0.1.0 90 | */ 91 | inline fun QueryGateway.query(queryName: String, query: Q): CompletableFuture { 92 | return this.query(queryName, query, ResponseTypes.instanceOf(R::class.java)) 93 | } 94 | 95 | /** 96 | * Reified version of [QueryGateway.query] 97 | * which expects an Optional object as a response using [org.axonframework.messaging.responsetypes.OptionalResponseType] 98 | * @param query Query to send 99 | * @param Q the type of payload of the query 100 | * @param R the type of result of the query 101 | * @return [CompletableFuture] wrapping the result of the query 102 | * @see QueryGateway.query 103 | * @see ResponseTypes 104 | * @since 0.1.0 105 | */ 106 | inline fun QueryGateway.queryOptional(query: Q): CompletableFuture> { 107 | return this.query(query, ResponseTypes.optionalInstanceOf(R::class.java)) 108 | } 109 | 110 | /** 111 | * Reified version of [QueryGateway.query] with explicit query name 112 | * which expects an Optional object as a response using [org.axonframework.messaging.responsetypes.OptionalResponseType] 113 | * @param queryName Name of the query 114 | * @param query Query to send 115 | * @param Q the type of payload of the query 116 | * @param R the type of result of the query 117 | * @return [CompletableFuture] wrapping the result of the query 118 | * @see QueryGateway.query 119 | * @see ResponseTypes 120 | * @since 0.1.0 121 | */ 122 | inline fun QueryGateway.queryOptional(queryName: String, query: Q): CompletableFuture> { 123 | return this.query(queryName, query, ResponseTypes.optionalInstanceOf(R::class.java)) 124 | } 125 | 126 | /** 127 | * Reified version of [QueryGateway.scatterGather] 128 | * which expects an Stream object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 129 | * @param query Query to send 130 | * @param timeout a timeout for the query 131 | * @param timeUnit the selected TimeUnit for the given timeout 132 | * @param [Q] the type of payload of the query 133 | * @param [R] the response class contained in the given responseType 134 | * @return [Stream] a stream of results 135 | * @see QueryGateway.scatterGather 136 | * @see ResponseTypes 137 | * @since 0.2.0 138 | */ 139 | inline fun QueryGateway.scatterGather(query: Q, timeout: Long, 140 | timeUnit: TimeUnit): Stream { 141 | return this.scatterGather(query, ResponseTypes.instanceOf(R::class.java), timeout, timeUnit) 142 | } 143 | 144 | /** 145 | * Reified version of [QueryGateway.scatterGather] with explicit query name 146 | * which expects an Stream object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 147 | * @param query Query to send 148 | * @param queryName Name of the query 149 | * @param timeout a timeout for the query 150 | * @param timeUnit the selected TimeUnit for the given timeout 151 | * @param [Q] the type of payload of the query 152 | * @param [R] the response class contained in the given responseType 153 | * @return [Stream] a stream of results 154 | * @see QueryGateway.scatterGather 155 | * @see ResponseTypes 156 | * @since 0.2.0 157 | */ 158 | inline fun QueryGateway.scatterGather(queryName: String, query: Q, timeout: Long, 159 | timeUnit: TimeUnit): Stream { 160 | return this.scatterGather(queryName, query, ResponseTypes.instanceOf(R::class.java), timeout, timeUnit) 161 | } 162 | 163 | /** 164 | * Reified version of [QueryGateway.scatterGather] 165 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.MultipleInstancesResponseType] 166 | * @param query Query to send 167 | * @param timeout a timeout for the query 168 | * @param timeUnit the selected TimeUnit for the given timeout 169 | * @param [Q] the type of payload of the query 170 | * @param [R] the response class contained in the given responseType 171 | * @return [Stream] a stream of results 172 | * @see QueryGateway.scatterGather 173 | * @see ResponseTypes 174 | * @since 0.2.0 175 | */ 176 | inline fun QueryGateway.scatterGatherMany(query: Q, timeout: Long, 177 | timeUnit: TimeUnit): Stream> { 178 | return this.scatterGather(query, ResponseTypes.multipleInstancesOf(R::class.java), timeout, timeUnit) 179 | } 180 | 181 | /** 182 | * Reified version of [QueryGateway.scatterGather] with explicit query name 183 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.MultipleInstancesResponseType] 184 | * @param query Query to send 185 | * @param queryName Name of the query 186 | * @param timeout a timeout for the query 187 | * @param timeUnit the selected TimeUnit for the given timeout 188 | * @param [Q] the type of payload of the query 189 | * @param [R] the response class contained in the given responseType 190 | * @return [Stream] a stream of results 191 | * @see QueryGateway.scatterGather 192 | * @see ResponseTypes 193 | * @since 0.2.0 194 | */ 195 | inline fun QueryGateway.scatterGatherMany(queryName: String, query: Q, timeout: Long, 196 | timeUnit: TimeUnit): Stream> { 197 | return this.scatterGather(queryName, query, ResponseTypes.multipleInstancesOf(R::class.java), timeout, timeUnit) 198 | } 199 | 200 | /** 201 | * Reified version of [QueryGateway.scatterGather] 202 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.OptionalResponseType] 203 | * @param query Query to send 204 | * @param timeout a timeout for the query 205 | * @param timeUnit the selected TimeUnit for the given timeout 206 | * @param [Q] the type of payload of the query 207 | * @param [R] the response class contained in the given responseType 208 | * @return [Stream] a stream of results 209 | * @see QueryGateway.scatterGather 210 | * @see ResponseTypes 211 | * @since 0.2.0 212 | */ 213 | inline fun QueryGateway.scatterGatherOptional(query: Q, timeout: Long, 214 | timeUnit: TimeUnit): Stream> { 215 | return this.scatterGather(query, ResponseTypes.optionalInstanceOf(R::class.java), timeout, timeUnit) 216 | } 217 | 218 | /** 219 | * Reified version of [QueryGateway.scatterGather] with explicit query name 220 | * which expects a collection as a response using [org.axonframework.messaging.responsetypes.OptionalResponseType] 221 | * @param query Query to send 222 | * @param queryName Name of the query 223 | * @param timeout a timeout for the query 224 | * @param timeUnit the selected TimeUnit for the given timeout 225 | * @param [Q] the type of payload of the query 226 | * @param [R] the response class contained in the given responseType 227 | * @return [Stream] a stream of results 228 | * @see QueryGateway.scatterGather 229 | * @see ResponseTypes 230 | * @since 0.2.0 231 | */ 232 | inline fun QueryGateway.scatterGatherOptional(queryName: String, query: Q, timeout: Long, 233 | timeUnit: TimeUnit): Stream> { 234 | return this.scatterGather(queryName, query, ResponseTypes.optionalInstanceOf(R::class.java), timeout, timeUnit) 235 | } 236 | 237 | /** 238 | * Reified version of [QueryGateway.subscriptionQuery] 239 | * which expects a single object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 240 | * @param query Query to send 241 | * @param Q the type of payload of the query 242 | * @param I the type of initial response 243 | * @param U the type of update response 244 | * @return [SubscriptionQueryResult] wrapping the result of the query 245 | * @see QueryGateway.subscriptionQuery 246 | * @see ResponseTypes 247 | * @since 0.3.0 248 | */ 249 | inline fun QueryGateway.subscriptionQuery(query: Q): SubscriptionQueryResult = 250 | this.subscriptionQuery(query, ResponseTypes.instanceOf(I::class.java), ResponseTypes.instanceOf(U::class.java)) 251 | 252 | /** 253 | * Reified version of [QueryGateway.subscriptionQuery] 254 | * which expects a single object as a response using [org.axonframework.messaging.responsetypes.InstanceResponseType] 255 | * @param queryName Name of the query 256 | * @param query Query to send 257 | * @param Q the type of payload of the query 258 | * @param I the type of initial response 259 | * @param U the type of update response 260 | * @return [SubscriptionQueryResult] wrapping the result of the query 261 | * @see QueryGateway.subscriptionQuery 262 | * @see ResponseTypes 263 | * @since 0.3.0 264 | */ 265 | inline fun QueryGateway.subscriptionQuery(queryName: String, query: Q): SubscriptionQueryResult = 266 | this.subscriptionQuery(queryName, query, ResponseTypes.instanceOf(I::class.java), ResponseTypes.instanceOf(U::class.java)) -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2023. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.queryhandling.QueryUpdateEmitter 20 | 21 | /** 22 | * Reified version of [org.axonframework.queryhandling.QueryUpdateEmitter.emit] which uses generics 23 | * to indicate Query type and Update type. 24 | * 25 | * Emits given incremental update to subscription queries matching given generic query type and filter. 26 | * In order to send nullable updates, use [org.axonframework.queryhandling.QueryUpdateEmitter.emit] 27 | * with an [org.axonframework.queryhandling.SubscriptionQueryUpdateMessage] 28 | * 29 | * @param update incremental update 30 | * @param filter predicate on query payload used to filter subscription queries 31 | * @param Q the type of the query 32 | * @param U the type of the update 33 | * @see org.axonframework.queryhandling.QueryUpdateEmitter.emit 34 | * @author Stefan Andjelkovic 35 | * @since 0.1.0 36 | */ 37 | inline fun QueryUpdateEmitter.emit(update: U, noinline filter: (Q) -> Boolean) = 38 | this.emit(Q::class.java, filter, update) 39 | 40 | 41 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/ResultDiscriminatorCommandCallback.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import org.axonframework.commandhandling.CommandCallback 19 | import org.axonframework.commandhandling.CommandMessage 20 | import org.axonframework.commandhandling.CommandResultMessage 21 | import org.axonframework.messaging.MetaData 22 | 23 | /** 24 | * Implementation of the [CommandCallback] that is appropriate for dedicated [onError] and [onSuccess] callbacks. 25 | * @param onError Callback to handle failed execution. Defaults to an empty function 26 | * @param onSuccess Callback to handle successful execution. Defaults to an empty function 27 | * @param [R] the type of result of the command handling 28 | * @param [C] the type of payload of the command 29 | * @see CommandCallback 30 | * @author Stefan Andjelkovic 31 | * @since 0.1.0 32 | */ 33 | class ResultDiscriminatorCommandCallback( 34 | val onSuccess: (commandMessage: CommandMessage, result: R, metaData: MetaData) -> Unit, 35 | val onError: (commandMessage: CommandMessage, exception: Throwable, metaData: MetaData) -> Unit 36 | ) : CommandCallback { 37 | override fun onResult(commandMessage: CommandMessage, commandResultMessage: CommandResultMessage) { 38 | val metaData: MetaData = commandResultMessage.metaData ?: MetaData.emptyInstance() 39 | if (commandResultMessage.isExceptional) { 40 | onError(commandMessage, commandResultMessage.exceptionResult(), metaData) 41 | } else { 42 | onSuccess(commandMessage, commandResultMessage.payload, metaData) 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/messaging/responsetypes/ArrayResponseType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.messaging.responsetypes 17 | 18 | import org.axonframework.common.ReflectionUtils 19 | import org.axonframework.common.TypeReflectionUtils 20 | import org.axonframework.messaging.responsetypes.AbstractResponseType 21 | import org.axonframework.messaging.responsetypes.InstanceResponseType 22 | import org.axonframework.messaging.responsetypes.ResponseType 23 | import java.lang.reflect.Type 24 | import java.util.concurrent.Future 25 | 26 | /** 27 | * A [ResponseType] implementation that will match with query handlers which return a multiple instances of the 28 | * expected response type. If matching succeeds, the [ResponseType.convert] function will be called, which 29 | * will cast the query handler it's response to an [Array] with element type [E]. 30 | * 31 | * @param E The element type which will be matched against and converted to 32 | * @author Gerard de Leeuw 33 | * @since 4.10.0 34 | * @see org.axonframework.messaging.responsetypes.MultipleInstancesResponseType 35 | */ 36 | class ArrayResponseType(elementType: Class) : AbstractResponseType>(elementType) { 37 | 38 | companion object { 39 | /** 40 | * Indicates that the response matches with the [Type] while returning an iterable result. 41 | * 42 | * @see ResponseType.MATCH 43 | * 44 | * @see ResponseType.NO_MATCH 45 | */ 46 | const val ITERABLE_MATCH = 1024 47 | } 48 | 49 | private val instanceResponseType: InstanceResponseType = InstanceResponseType(elementType) 50 | 51 | /** 52 | * Match the query handler's response [Type] with this implementation's [E]. 53 | * Will return true in the following scenarios: 54 | * 55 | * * If the response type is an [Array] 56 | * * If the response type is a [E] 57 | * 58 | * If there is no match at all, it will return false to indicate a non-match. 59 | * 60 | * @param responseType the response [Type] of the query handler which is matched against 61 | * @return true for [Array] or [E] and [ResponseType.NO_MATCH] for non-matches 62 | */ 63 | override fun matches(responseType: Type): Boolean = 64 | matchRank(responseType) > NO_MATCH 65 | 66 | /** 67 | * Match the query handler's response [Type] with this implementation's [E]. 68 | * Will return a value greater than 0 in the following scenarios: 69 | * 70 | * * [ITERABLE_MATCH]: If the response type is an [Array] 71 | * * [ResponseType.MATCH]: If the response type is a [E] 72 | * 73 | * If there is no match at all, it will return [ResponseType.NO_MATCH] to indicate a non-match. 74 | * 75 | * @param responseType the response [Type] of the query handler which is matched against 76 | * @return [ITERABLE_MATCH] for [Array], [ResponseType.MATCH] for [E] and [ResponseType.NO_MATCH] for non-matches 77 | */ 78 | override fun matchRank(responseType: Type): Int = when { 79 | isMatchingArray(responseType) -> ITERABLE_MATCH 80 | else -> instanceResponseType.matchRank(responseType) 81 | } 82 | 83 | /** 84 | * Converts the given [response] of type [Object] into an [Array] with element type [E] from 85 | * this [ResponseType] instance. Should only be called if [ResponseType.matches] returns true. 86 | * Will throw an [IllegalArgumentException] if the given response 87 | * is not convertible to an [Array] of the expected response type. 88 | * 89 | * @param response the [Object] to convert into an [Array] with element type [E] 90 | * @return an [Array] with element type [E], based on the given [response] 91 | */ 92 | override fun convert(response: Any): Array { 93 | val responseType: Class<*> = response.javaClass 94 | if (responseType.isArray) { 95 | @Suppress("UNCHECKED_CAST") 96 | return response as Array 97 | } 98 | throw IllegalArgumentException( 99 | "Retrieved response [$responseType] is not convertible to an array with the expected element type [$expectedResponseType]" 100 | ) 101 | } 102 | 103 | @Suppress("UNCHECKED_CAST") 104 | override fun responseMessagePayloadType(): Class> = 105 | Array::class.java as Class> 106 | 107 | override fun toString(): String = "ArrayResponseType[$expectedResponseType]" 108 | 109 | private fun isMatchingArray(responseType: Type): Boolean { 110 | val unwrapped = ReflectionUtils.unwrapIfType(responseType, Future::class.java) 111 | val iterableType = TypeReflectionUtils.getExactSuperType(unwrapped, Array::class.java) 112 | return iterableType != null && isParameterizedTypeOfExpectedType(iterableType) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/KotlinSerializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serialization 17 | 18 | import kotlinx.serialization.* 19 | import kotlinx.serialization.SerializationException 20 | import kotlinx.serialization.builtins.ArraySerializer 21 | import kotlinx.serialization.builtins.ListSerializer 22 | import kotlinx.serialization.builtins.SetSerializer 23 | import kotlinx.serialization.builtins.serializer 24 | import org.axonframework.serialization.* 25 | import org.axonframework.serialization.Serializer 26 | import java.util.concurrent.ConcurrentHashMap 27 | import org.axonframework.serialization.SerializationException as AxonSerializationException 28 | 29 | /** 30 | * Implementation of Axon [Serializer] that uses a [kotlinx.serialization] implementation. 31 | * 32 | * When instantiating the [KotlinSerializer], use the [AxonSerializersModule] to insert serializers for all common 33 | * Axon Framework components. 34 | * When the desired serialization format is JSON, the construction of the serializer would look as follows: 35 | * `KotlinSerializer(serialFormat = Json { serializersModule = AxonSerializersModule })` 36 | * 37 | * Other construction parameters of the [KotlinSerializer] are the [RevisionResolver] 38 | * (defaulted to a [AnnotationRevisionResolver]) and [Converter] (defaulted to a [ChainingConverter]). 39 | * 40 | * @see kotlinx.serialization.Serializer 41 | * @see org.axonframework.serialization.Serializer 42 | * 43 | * @since 4.10.0 44 | * @author Gerard de Leeuw 45 | */ 46 | class KotlinSerializer( 47 | private val serialFormat: SerialFormat, 48 | private val revisionResolver: RevisionResolver = AnnotationRevisionResolver(), 49 | private val converter: Converter = ChainingConverter(), 50 | ) : Serializer { 51 | 52 | private val serializerCache: MutableMap, KSerializer<*>> = ConcurrentHashMap() 53 | private val unknownSerializedType = UnknownSerializedType::class.java 54 | 55 | override fun getConverter(): Converter = converter 56 | 57 | override fun canSerializeTo(expectedRepresentation: Class): Boolean = when (serialFormat) { 58 | is StringFormat -> converter.canConvert(String::class.java, expectedRepresentation) 59 | is BinaryFormat -> converter.canConvert(ByteArray::class.java, expectedRepresentation) 60 | else -> false 61 | } 62 | 63 | override fun serialize(value: Any?, expectedRepresentation: Class): SerializedObject { 64 | try { 65 | val serializedType = typeForValue(value) 66 | val classForType = classForType(serializedType) 67 | 68 | val serializer = serializerFor(classForType, serializedType) 69 | val serialized: SerializedObject<*> = when (serialFormat) { 70 | is StringFormat -> SimpleSerializedObject( 71 | serialFormat.encodeToString(serializer, value), 72 | String::class.java, 73 | serializedType 74 | ) 75 | 76 | is BinaryFormat -> SimpleSerializedObject( 77 | serialFormat.encodeToByteArray(serializer, value), 78 | ByteArray::class.java, 79 | serializedType 80 | ) 81 | 82 | else -> throw SerializationException("Unsupported serial format: $serialFormat") 83 | } 84 | 85 | return converter.convert(serialized, expectedRepresentation) 86 | } catch (ex: SerializationException) { 87 | throw AxonSerializationException("Cannot serialize type ${value?.javaClass?.name} to representation $expectedRepresentation.", ex) 88 | } 89 | } 90 | 91 | @Suppress("UNCHECKED_CAST") 92 | override fun deserialize(serializedObject: SerializedObject): T? { 93 | try { 94 | if (serializedObject.type == SerializedType.emptyType()) { 95 | return null 96 | } 97 | 98 | val classForType = classForType(serializedObject.type) 99 | if (unknownSerializedType.isAssignableFrom(classForType)) { 100 | return UnknownSerializedType(this, serializedObject) as T 101 | } 102 | 103 | val serializer = serializerFor(classForType, serializedObject.type) as KSerializer 104 | return when (serialFormat) { 105 | is StringFormat -> { 106 | val json = converter.convert(serializedObject, String::class.java).data 107 | when { 108 | json.isEmpty() -> null 109 | else -> serialFormat.decodeFromString(serializer, json) 110 | } 111 | } 112 | 113 | is BinaryFormat -> { 114 | val bytes = converter.convert(serializedObject, ByteArray::class.java).data 115 | when { 116 | bytes.isEmpty() -> null 117 | else -> serialFormat.decodeFromByteArray(serializer, bytes) 118 | } 119 | } 120 | 121 | else -> throw SerializationException("Unsupported serial format: $serialFormat") 122 | } 123 | } catch (ex: SerializationException) { 124 | throw AxonSerializationException("Could not deserialize from content type ${serializedObject.contentType} to type ${serializedObject.type}", ex) 125 | } 126 | } 127 | 128 | override fun classForType(type: SerializedType): Class<*> = when (val unwrapped = type.unwrap()) { 129 | SerializedType.emptyType() -> Void.TYPE 130 | else -> try { 131 | Class.forName(unwrapped.name) 132 | } catch (ex: ClassNotFoundException) { 133 | unknownSerializedType 134 | } 135 | } 136 | 137 | override fun typeForClass(type: Class<*>?): SerializedType = 138 | if (type == null || Void.TYPE == type || Void::class.java.isAssignableFrom(type)) { 139 | SimpleSerializedType.emptyType() 140 | } else { 141 | SimpleSerializedType(type.name, revisionResolver.revisionOf(type)) 142 | } 143 | 144 | private fun typeForValue(value: Any?): SerializedType = when (value) { 145 | null -> SimpleSerializedType.emptyType() 146 | Unit -> SimpleSerializedType.emptyType() 147 | is Class<*> -> typeForClass(value) 148 | is Array<*> -> typeForClass(value.javaClass.componentType).wrap("Array") 149 | is List<*> -> typeForClass(value.findCommonInterfaceOfEntries()).wrap("List") 150 | is Set<*> -> typeForClass(value.findCommonInterfaceOfEntries()).wrap("Set") 151 | else -> typeForClass(value.javaClass) 152 | } 153 | 154 | private fun SerializedType.wrap(wrapper: String) = SimpleSerializedType("$wrapper:$name", revision) 155 | private fun SerializedType.unwrap() = SimpleSerializedType(name.substringAfter(':'), revision) 156 | 157 | @Suppress("UNCHECKED_CAST", "OPT_IN_USAGE") 158 | private fun serializerFor(type: Class<*>, serializedType: SerializedType): KSerializer { 159 | val serializer = when { 160 | Void.TYPE == type || Void::class.java.isAssignableFrom(type) -> Unit.serializer() 161 | unknownSerializedType.isAssignableFrom(type) -> throw ClassNotFoundException("Can't load class for type $this") 162 | else -> serializerCache.computeIfAbsent(type, serialFormat.serializersModule::serializer) 163 | } as KSerializer 164 | 165 | return when (serializedType.name.substringBefore(':')) { 166 | "Array" -> ArraySerializer(serializer) 167 | "List" -> ListSerializer(serializer) 168 | "Set" -> SetSerializer(serializer) 169 | else -> serializer 170 | } as KSerializer 171 | } 172 | } 173 | 174 | private fun Iterable<*>.findCommonInterfaceOfEntries(): Class<*>? { 175 | val firstElement = firstOrNull() ?: return null 176 | 177 | val commonTypeInIterable = fold(firstElement.javaClass as Class<*>) { resultingClass, element -> 178 | if (element == null) { 179 | // Element is null, so we just assume the type of the previous elements 180 | resultingClass 181 | } else if (resultingClass.isAssignableFrom(element.javaClass)) { 182 | // Element matches the elements we've seen so far, so we can keep the type 183 | resultingClass 184 | } else { 185 | // Element does not match the elements we've seen so far, so we look for a common 186 | // interface and continue with that 187 | element.javaClass.interfaces.firstOrNull { it.isAssignableFrom(resultingClass) } 188 | ?: throw SerializationException("Cannot find interface for serialization") 189 | } 190 | } 191 | 192 | return commonTypeInIterable 193 | } 194 | -------------------------------------------------------------------------------- /kotlin/src/main/kotlin/org/axonframework/extensions/kotlin/serialization/MetaDataSerializer.kt: -------------------------------------------------------------------------------- 1 | package org.axonframework.extensions.kotlin.serialization 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.SerializationException 5 | import kotlinx.serialization.builtins.MapSerializer 6 | import kotlinx.serialization.builtins.serializer 7 | import kotlinx.serialization.descriptors.SerialDescriptor 8 | import kotlinx.serialization.encodeToString 9 | import kotlinx.serialization.encoding.Decoder 10 | import kotlinx.serialization.encoding.Encoder 11 | import kotlinx.serialization.json.* 12 | import org.axonframework.messaging.MetaData 13 | import java.time.Instant 14 | import java.util.UUID 15 | 16 | /** 17 | * A composite Kotlinx [KSerializer] for Axon Framework's [MetaData] type that selects the 18 | * appropriate serializer based on the encoder/decoder type. 19 | * 20 | * This serializer delegates to: 21 | * - [JsonMetaDataSerializer] when used with [JsonEncoder]/[JsonDecoder] 22 | * - [StringMetaDataSerializer] for all other encoder/decoder types 23 | * 24 | * This allows efficient JSON serialization without unnecessary string encoding, while 25 | * maintaining compatibility with all other serialization formats through string-based 26 | * serialization. 27 | * 28 | * @author Mateusz Nowak 29 | * @since 4.11.2 30 | */ 31 | object ComposedMetaDataSerializer : KSerializer { 32 | override val descriptor: SerialDescriptor = StringMetaDataSerializer.descriptor 33 | 34 | override fun serialize(encoder: Encoder, value: MetaData) { 35 | when (encoder) { 36 | is JsonEncoder -> JsonMetaDataSerializer.serialize(encoder, value) 37 | else -> StringMetaDataSerializer.serialize(encoder, value) 38 | } 39 | } 40 | 41 | override fun deserialize(decoder: Decoder): MetaData { 42 | return when (decoder) { 43 | is JsonDecoder -> JsonMetaDataSerializer.deserialize(decoder) 44 | else -> StringMetaDataSerializer.deserialize(decoder) 45 | } 46 | } 47 | } 48 | 49 | /** 50 | * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, suitable for serialization across any format. 51 | * 52 | * This serializer converts a [MetaData] instance to a JSON-encoded [String] using a recursive conversion 53 | * of all entries into [JsonElement]s. This JSON string is then serialized using [String.serializer()], 54 | * ensuring compatibility with any serialization format. 55 | * 56 | * ### Supported value types 57 | * Each entry in the MetaData map must conform to one of the following: 58 | * - Primitives: [String], [Int], [Long], [Float], [Double], [Boolean] 59 | * - Complex types: [UUID], [Instant] 60 | * - Collections: [Collection], [List], [Set] 61 | * - Arrays: [Array] 62 | * - Nested Maps: [Map] with keys convertible to [String] 63 | * 64 | * ### Limitations 65 | * - Custom types that do not fall into the above categories will throw a [SerializationException] 66 | * - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types 67 | * 68 | * This serializer guarantees structural integrity of nested metadata while remaining format-agnostic. 69 | * 70 | * @author Mateusz Nowak 71 | * @since 4.11.1 72 | */ 73 | object StringMetaDataSerializer : KSerializer { 74 | private val json = Json { encodeDefaults = true; ignoreUnknownKeys = true } 75 | 76 | override val descriptor: SerialDescriptor = String.serializer().descriptor 77 | 78 | override fun serialize(encoder: Encoder, value: MetaData) { 79 | val map: Map = value.entries.associate { (key, rawValue) -> 80 | key to toJsonElement(rawValue) 81 | } 82 | val jsonString = json.encodeToString(JsonObject(map)) 83 | encoder.encodeString(jsonString) 84 | } 85 | 86 | override fun deserialize(decoder: Decoder): MetaData { 87 | val jsonString = decoder.decodeString() 88 | val jsonObject = json.parseToJsonElement(jsonString).jsonObject 89 | val reconstructed = jsonObject.mapValues { (_, jsonElement) -> 90 | fromJsonElement(jsonElement) 91 | } 92 | return MetaData(reconstructed) 93 | } 94 | 95 | private fun toJsonElement(value: Any?): JsonElement = when (value) { 96 | null -> JsonNull 97 | is String -> JsonPrimitive(value) 98 | is Boolean -> JsonPrimitive(value) 99 | is Int -> JsonPrimitive(value) 100 | is Long -> JsonPrimitive(value) 101 | is Float -> JsonPrimitive(value) 102 | is Double -> JsonPrimitive(value) 103 | is UUID -> JsonPrimitive(value.toString()) 104 | is Instant -> JsonPrimitive(value.toString()) 105 | is Map<*, *> -> JsonObject(value.entries.associate { (k, v) -> 106 | k.toString() to toJsonElement(v) 107 | }) 108 | is Collection<*> -> JsonArray(value.map { toJsonElement(it) }) 109 | is Array<*> -> JsonArray(value.map { toJsonElement(it) }) 110 | else -> throw SerializationException("Unsupported type: ${value::class}") 111 | } 112 | 113 | private fun fromJsonElement(element: JsonElement): Any? = when (element) { 114 | is JsonNull -> null 115 | is JsonPrimitive -> { 116 | if (element.isString) { 117 | element.content 118 | } else { 119 | element.booleanOrNull ?: element.intOrNull ?: element.longOrNull ?: 120 | element.floatOrNull ?: element.doubleOrNull ?: element.content 121 | } 122 | } 123 | is JsonObject -> element.mapValues { fromJsonElement(it.value) } 124 | is JsonArray -> element.map { fromJsonElement(it) } 125 | } 126 | } 127 | 128 | /** 129 | * A Kotlinx [KSerializer] for Axon Framework's [MetaData] type, optimized for JSON serialization. 130 | * 131 | * This serializer converts a [MetaData] instance directly to a JSON object structure, 132 | * avoiding the string-encoding that [StringMetaDataSerializer] uses. This ensures JSON values 133 | * are properly encoded without quote escaping. 134 | * 135 | * ### Supported value types 136 | * Each entry in the MetaData map must conform to one of the following: 137 | * - Primitives: [String], [Int], [Long], [Float], [Double], [Boolean] 138 | * - Complex types: [UUID], [Instant] 139 | * - Collections: [Collection], [List], [Set] 140 | * - Arrays: [Array] 141 | * - Nested Maps: [Map] with keys convertible to [String] 142 | * 143 | * ### Limitations 144 | * - Custom types that do not fall into the above categories will throw a [SerializationException] 145 | * - Deserialized non-primitive types (like [UUID], [Instant]) are restored as [String], not their original types 146 | * 147 | * This serializer is specifically optimized for JSON serialization formats. 148 | * 149 | * @author Mateusz Nowak 150 | * @since 4.11.2 151 | */ 152 | object JsonMetaDataSerializer : KSerializer { 153 | private val mapSerializer = MapSerializer(String.serializer(), JsonElement.serializer()) 154 | 155 | override val descriptor: SerialDescriptor = mapSerializer.descriptor 156 | 157 | override fun serialize(encoder: Encoder, value: MetaData) { 158 | val jsonMap = value.entries.associate { (key, rawValue) -> 159 | key to toJsonElement(rawValue) 160 | } 161 | encoder.encodeSerializableValue(mapSerializer, jsonMap) 162 | } 163 | 164 | override fun deserialize(decoder: Decoder): MetaData { 165 | val jsonMap = decoder.decodeSerializableValue(mapSerializer) 166 | val reconstructed = jsonMap.mapValues { (_, jsonElement) -> 167 | fromJsonElement(jsonElement) 168 | } 169 | return MetaData(reconstructed) 170 | } 171 | 172 | private fun toJsonElement(value: Any?): JsonElement = when (value) { 173 | null -> JsonNull 174 | is String -> JsonPrimitive(value) 175 | is Boolean -> JsonPrimitive(value) 176 | is Int -> JsonPrimitive(value) 177 | is Long -> JsonPrimitive(value) 178 | is Float -> JsonPrimitive(value) 179 | is Double -> JsonPrimitive(value) 180 | is UUID -> JsonPrimitive(value.toString()) 181 | is Instant -> JsonPrimitive(value.toString()) 182 | is Map<*, *> -> JsonObject(value.entries.associate { (k, v) -> 183 | k.toString() to toJsonElement(v) 184 | }) 185 | is Collection<*> -> JsonArray(value.map { toJsonElement(it) }) 186 | is Array<*> -> JsonArray(value.map { toJsonElement(it) }) 187 | else -> throw SerializationException("Unsupported type: ${value::class}") 188 | } 189 | 190 | private fun fromJsonElement(element: JsonElement): Any? = when (element) { 191 | is JsonNull -> null 192 | is JsonPrimitive -> { 193 | if (element.isString) { 194 | element.content 195 | } else { 196 | element.booleanOrNull ?: element.intOrNull ?: element.longOrNull ?: 197 | element.floatOrNull ?: element.doubleOrNull ?: element.content 198 | } 199 | } 200 | is JsonObject -> element.mapValues { fromJsonElement(it.value) } 201 | is JsonArray -> element.map { fromJsonElement(it) } 202 | } 203 | } -------------------------------------------------------------------------------- /kotlin/src/main/resources/META-INF/spring-devtools.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2010-2023. Axon Framework 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | restart.include.axon-kotlin=axon-kotlin-${project.version}.jar 17 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/CommandGatewayExtensionsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import io.mockk.* 19 | import org.axonframework.commandhandling.CommandCallback 20 | import org.axonframework.commandhandling.gateway.CommandGateway 21 | import org.axonframework.messaging.MetaData 22 | import kotlin.test.Test 23 | 24 | /** 25 | * Tests Command Gateway extensions. 26 | * 27 | * @author Stefan Andjelkovic 28 | */ 29 | internal class CommandGatewayExtensionsTest { 30 | private val subjectGateway = mockk() 31 | 32 | private val exampleCommand = ExampleCommand("1") 33 | 34 | @Test 35 | fun `Send extension should invoke correct method on the gateway`() { 36 | every { subjectGateway.send(exampleCommand, any>()) } just Runs 37 | 38 | subjectGateway.send( 39 | command = exampleCommand, 40 | onError = { _, _, _ -> }, 41 | onSuccess = { _, _: Any, _ -> } 42 | ) 43 | 44 | verify { subjectGateway.send(exampleCommand, any>()) } 45 | } 46 | 47 | @Test 48 | fun `Send extension should invoke correct method on the gateway without explicit generic parameters`() { 49 | every { subjectGateway.send(exampleCommand, any>()) } just Runs 50 | 51 | subjectGateway.send( 52 | command = exampleCommand, 53 | onError = { _, _: Throwable, _: MetaData -> }, 54 | onSuccess = { _, _: Any, _: MetaData -> } 55 | ) 56 | 57 | verify { subjectGateway.send(exampleCommand, any>()) } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryGatewayExtensionsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import io.mockk.clearMocks 19 | import io.mockk.every 20 | import io.mockk.mockk 21 | import io.mockk.verify 22 | import org.axonframework.queryhandling.QueryGateway 23 | import org.axonframework.queryhandling.SubscriptionQueryResult 24 | import java.util.* 25 | import java.util.concurrent.CompletableFuture 26 | import java.util.concurrent.TimeUnit 27 | import java.util.stream.Stream 28 | import kotlin.test.* 29 | 30 | /** 31 | * Tests Query Gateway extensions. 32 | * 33 | * @author Stefan Andjelkovic 34 | * @author Henrique Sena 35 | */ 36 | internal class QueryGatewayExtensionsTest { 37 | 38 | private val queryName = ExampleQuery::class.qualifiedName.toString() 39 | private val exampleQuery = ExampleQuery(2) 40 | private val instanceReturnValue: CompletableFuture = CompletableFuture.completedFuture("2") 41 | private val optionalReturnValue: CompletableFuture> = CompletableFuture.completedFuture(Optional.of("Value")) 42 | private val listReturnValue: CompletableFuture> = CompletableFuture.completedFuture(listOf("Value", "Second value")) 43 | private val subjectGateway = mockk() 44 | private val timeout: Long = 1000 45 | private val timeUnit = TimeUnit.SECONDS 46 | private val streamInstanceReturnValue = Stream.of("Value") 47 | private val streamMultipleReturnValue = Stream.of(listOf("Value", "Second Value")) 48 | private val streamOptionalReturnValue = Stream.of(Optional.of("Value")) 49 | private val subscriptionQueryResult = ExampleSubscriptionQueryResult() 50 | 51 | @BeforeTest 52 | fun before() { 53 | every { subjectGateway.query(exampleQuery, matchInstanceResponseType()) } returns instanceReturnValue 54 | every { subjectGateway.query(exampleQuery, matchOptionalResponseType()) } returns optionalReturnValue 55 | every { subjectGateway.query(exampleQuery, matchMultipleInstancesResponseType()) } returns listReturnValue 56 | every { subjectGateway.query(queryName, exampleQuery, matchInstanceResponseType()) } returns instanceReturnValue 57 | every { subjectGateway.query(queryName, exampleQuery, matchOptionalResponseType()) } returns optionalReturnValue 58 | every { subjectGateway.query(queryName, exampleQuery, matchMultipleInstancesResponseType()) } returns listReturnValue 59 | every { subjectGateway.scatterGather(exampleQuery, matchInstanceResponseType(), timeout, timeUnit) } returns streamInstanceReturnValue 60 | every { subjectGateway.scatterGather(exampleQuery, matchMultipleInstancesResponseType(), timeout, timeUnit) } returns streamMultipleReturnValue 61 | every { subjectGateway.scatterGather(exampleQuery, matchOptionalResponseType(), timeout, timeUnit) } returns streamOptionalReturnValue 62 | every { subjectGateway.scatterGather(queryName, exampleQuery, matchInstanceResponseType(), timeout, timeUnit) } returns streamInstanceReturnValue 63 | every { subjectGateway.scatterGather(queryName, exampleQuery, matchMultipleInstancesResponseType(), timeout, timeUnit) } returns streamMultipleReturnValue 64 | every { subjectGateway.scatterGather(queryName, exampleQuery, matchOptionalResponseType(), timeout, timeUnit) } returns streamOptionalReturnValue 65 | every { subjectGateway.subscriptionQuery(exampleQuery, matchInstanceResponseType(), matchInstanceResponseType()) } returns subscriptionQueryResult 66 | every { 67 | subjectGateway.subscriptionQuery( 68 | queryName, 69 | exampleQuery, 70 | matchInstanceResponseType(), 71 | matchInstanceResponseType() 72 | ) 73 | } returns subscriptionQueryResult 74 | } 75 | 76 | @AfterTest 77 | fun after() { 78 | clearMocks(subjectGateway) 79 | } 80 | 81 | @Test 82 | fun `Query without queryName should invoke query method with correct generic parameters`() { 83 | val queryResult = subjectGateway.query(query = exampleQuery) 84 | assertSame(queryResult, instanceReturnValue) 85 | verify(exactly = 1) { 86 | subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) 87 | } 88 | } 89 | 90 | @Test 91 | fun `Query without queryName should invoke query method and not require explicit generic types`() { 92 | val queryResult: CompletableFuture = subjectGateway.query(query = exampleQuery) 93 | assertSame(queryResult, instanceReturnValue) 94 | verify(exactly = 1) { 95 | subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) 96 | } 97 | } 98 | 99 | @Test 100 | fun `Query without queryName Optional should invoke query method with correct generic parameters`() { 101 | val queryResult = subjectGateway.queryOptional(query = exampleQuery) 102 | assertSame(queryResult, optionalReturnValue) 103 | verify(exactly = 1) { subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) } 104 | } 105 | 106 | @Test 107 | fun `Query without queryName Optional should invoke query method and not require explicit generic types`() { 108 | val queryResult: CompletableFuture> = subjectGateway.queryOptional(query = exampleQuery) 109 | 110 | assertSame(queryResult, optionalReturnValue) 111 | verify(exactly = 1) { subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) } 112 | } 113 | 114 | @Test 115 | fun `Query without queryName Multiple should invoke query method with correct generic parameters`() { 116 | val queryResult = subjectGateway.queryMany(query = exampleQuery) 117 | 118 | assertSame(queryResult, listReturnValue) 119 | verify(exactly = 1) { subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) } 120 | } 121 | 122 | @Test 123 | fun `Query without queryName Multiple should invoke query method and not require explicit generic types`() { 124 | val queryResult: CompletableFuture> = subjectGateway.queryMany(query = exampleQuery) 125 | 126 | assertSame(queryResult, listReturnValue) 127 | verify(exactly = 1) { subjectGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) } 128 | } 129 | 130 | @Test 131 | fun `Query without queryName should handle nullable responses`() { 132 | val nullInstanceReturnValue: CompletableFuture = CompletableFuture.completedFuture(null) 133 | val nullableQueryGateway = mockk { 134 | every { query(exampleQuery, matchInstanceResponseType()) } returns nullInstanceReturnValue 135 | } 136 | 137 | val queryResult = nullableQueryGateway.query(query = exampleQuery) 138 | 139 | assertSame(queryResult, nullInstanceReturnValue) 140 | assertEquals(nullInstanceReturnValue.get(), null) 141 | verify(exactly = 1) { nullableQueryGateway.query(exampleQuery, matchExpectedResponseType(String::class.java)) } 142 | } 143 | 144 | 145 | @Test 146 | fun `Query should invoke query method with correct generic parameters`() { 147 | val queryResult = subjectGateway.query(queryName = queryName, query = exampleQuery) 148 | assertSame(queryResult, instanceReturnValue) 149 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 150 | } 151 | 152 | @Test 153 | fun `Query should invoke query method and not require explicit generic types`() { 154 | val queryResult: CompletableFuture = subjectGateway.query(queryName = queryName, query = exampleQuery) 155 | 156 | assertSame(queryResult, instanceReturnValue) 157 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 158 | } 159 | 160 | @Test 161 | fun `Query Optional should invoke query method with correct generic parameters`() { 162 | val queryResult = subjectGateway.queryOptional(queryName = queryName, query = exampleQuery) 163 | 164 | assertSame(queryResult, optionalReturnValue) 165 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 166 | } 167 | 168 | @Test 169 | fun `Query Optional should invoke query method and not require explicit generic types`() { 170 | val queryResult: CompletableFuture> = subjectGateway.queryOptional(queryName = queryName, query = exampleQuery) 171 | 172 | assertSame(queryResult, optionalReturnValue) 173 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 174 | } 175 | 176 | @Test 177 | fun `Query Multiple should invoke query method with correct generic parameters`() { 178 | val queryResult = subjectGateway.queryMany(queryName = queryName, query = exampleQuery) 179 | 180 | assertSame(queryResult, listReturnValue) 181 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 182 | } 183 | 184 | @Test 185 | fun `Query Multiple should invoke query method and not require explicit generic types`() { 186 | val queryResult: CompletableFuture> = subjectGateway.queryMany(queryName = queryName, query = exampleQuery) 187 | 188 | assertSame(queryResult, listReturnValue) 189 | verify(exactly = 1) { subjectGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 190 | } 191 | 192 | @Test 193 | fun `Query should handle nullable responses`() { 194 | val nullInstanceReturnValue: CompletableFuture = CompletableFuture.completedFuture(null) 195 | val nullableQueryGateway = mockk { 196 | every { query(queryName, exampleQuery, matchInstanceResponseType()) } returns nullInstanceReturnValue 197 | } 198 | 199 | val queryResult = nullableQueryGateway.query(queryName = queryName, query = exampleQuery) 200 | 201 | assertSame(queryResult, nullInstanceReturnValue) 202 | assertEquals(nullInstanceReturnValue.get(), null) 203 | verify(exactly = 1) { nullableQueryGateway.query(queryName, exampleQuery, matchExpectedResponseType(String::class.java)) } 204 | } 205 | 206 | @Test 207 | fun `ScatterGather for Single should invoke scatterGather method with correct generic parameters`() { 208 | val result = subjectGateway.scatterGather( 209 | query = exampleQuery, 210 | timeout = timeout, 211 | timeUnit = timeUnit 212 | ) 213 | 214 | assertSame(result, streamInstanceReturnValue) 215 | verify(exactly = 1) { subjectGateway.scatterGather(exampleQuery, matchExpectedResponseType(String::class.java), timeout, timeUnit) } 216 | } 217 | 218 | @Test 219 | fun `ScatterGather for Multiple should invoke scatterGather method with correct generic parameters`() { 220 | val result = subjectGateway.scatterGatherMany( 221 | query = exampleQuery, 222 | timeout = timeout, 223 | timeUnit = timeUnit 224 | ) 225 | 226 | assertSame(result, streamMultipleReturnValue) 227 | verify(exactly = 1) { subjectGateway.scatterGather(exampleQuery, matchMultipleInstancesResponseType(), timeout, timeUnit) } 228 | } 229 | 230 | @Test 231 | fun `ScatterGather for Optional should invoke scatterGather method with correct generic parameters`() { 232 | val result = subjectGateway.scatterGatherOptional( 233 | query = exampleQuery, 234 | timeout = timeout, 235 | timeUnit = timeUnit 236 | ) 237 | 238 | assertSame(result, streamOptionalReturnValue) 239 | verify(exactly = 1) { subjectGateway.scatterGather(exampleQuery, matchOptionalResponseType(), timeout, timeUnit) } 240 | } 241 | 242 | @Test 243 | fun `ScatterGather for Single should invoke scatterGather method with explicit query name`() { 244 | val result = subjectGateway.scatterGather( 245 | queryName = queryName, 246 | query = exampleQuery, 247 | timeout = timeout, 248 | timeUnit = timeUnit 249 | ) 250 | 251 | assertSame(result, streamInstanceReturnValue) 252 | verify(exactly = 1) { subjectGateway.scatterGather(queryName, exampleQuery, matchExpectedResponseType(String::class.java), timeout, timeUnit) } 253 | } 254 | 255 | @Test 256 | fun `ScatterGather for Multiple should invoke scatterGather method with explicit query name`() { 257 | val result = subjectGateway.scatterGatherMany( 258 | queryName = queryName, 259 | query = exampleQuery, 260 | timeout = timeout, 261 | timeUnit = timeUnit 262 | ) 263 | 264 | assertSame(result, streamMultipleReturnValue) 265 | verify(exactly = 1) { subjectGateway.scatterGather(queryName, exampleQuery, matchMultipleInstancesResponseType(), timeout, timeUnit) } 266 | } 267 | 268 | @Test 269 | fun `ScatterGather for Optional should invoke scatterGather method with explicit query name`() { 270 | val result = subjectGateway.scatterGatherOptional( 271 | queryName = queryName, 272 | query = exampleQuery, 273 | timeout = timeout, 274 | timeUnit = timeUnit 275 | ) 276 | 277 | assertSame(result, streamOptionalReturnValue) 278 | verify(exactly = 1) { subjectGateway.scatterGather(queryName, exampleQuery, matchOptionalResponseType(), timeout, timeUnit) } 279 | } 280 | 281 | @Test 282 | fun `Query without queryName should invoke subscription query method with correct generic parameters`() { 283 | val queryResult = subjectGateway.subscriptionQuery(query = exampleQuery) 284 | assertSame(queryResult, subscriptionQueryResult) 285 | verify(exactly = 1) { 286 | subjectGateway.subscriptionQuery( 287 | exampleQuery, 288 | matchExpectedResponseType(InitialResponseType::class.java), 289 | matchExpectedResponseType(UpdateResponseType::class.java) 290 | ) 291 | } 292 | } 293 | 294 | @Test 295 | fun `Query without queryName should invoke subscriptionQuery method and not require explicit generic types`() { 296 | val queryResult: SubscriptionQueryResult = subjectGateway.subscriptionQuery(query = exampleQuery) 297 | assertSame(queryResult, subscriptionQueryResult) 298 | verify(exactly = 1) { 299 | subjectGateway.subscriptionQuery( 300 | exampleQuery, 301 | matchExpectedResponseType(InitialResponseType::class.java), 302 | matchExpectedResponseType(UpdateResponseType::class.java) 303 | ) 304 | } 305 | } 306 | 307 | @Test 308 | fun `Query should invoke subscriptionQuery method with correct generic parameters`() { 309 | val queryResult = subjectGateway.subscriptionQuery(queryName = queryName, query = exampleQuery) 310 | assertSame(queryResult, subscriptionQueryResult) 311 | verify(exactly = 1) { 312 | subjectGateway.subscriptionQuery( 313 | queryName, 314 | exampleQuery, 315 | matchExpectedResponseType(InitialResponseType::class.java), 316 | matchExpectedResponseType(UpdateResponseType::class.java) 317 | ) 318 | } 319 | } 320 | 321 | @Test 322 | fun `Query should invoke subscriptionQuery method and not require explicit generic types`() { 323 | val queryResult: SubscriptionQueryResult = subjectGateway.subscriptionQuery(queryName = queryName, query = exampleQuery) 324 | assertSame(queryResult, subscriptionQueryResult) 325 | verify(exactly = 1) { 326 | subjectGateway.subscriptionQuery( 327 | queryName, 328 | exampleQuery, 329 | matchExpectedResponseType(InitialResponseType::class.java), 330 | matchExpectedResponseType(UpdateResponseType::class.java) 331 | ) 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/QueryUpdateEmitterExtensionsTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2023. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import io.mockk.every 20 | import io.mockk.mockk 21 | import io.mockk.verify 22 | import org.axonframework.queryhandling.QueryUpdateEmitter 23 | import kotlin.test.BeforeTest 24 | import kotlin.test.Test 25 | 26 | /** 27 | * Tests [org.axonframework.queryhandling.QueryUpdateEmitter] extensions. 28 | * 29 | * @author Stefan Andjelkovic 30 | */ 31 | internal class QueryUpdateEmitterExtensionsTest { 32 | private val subjectEmitter = mockk() 33 | private val exampleQuery = ExampleQuery(2) 34 | private val exampleUpdatePayload: String = "Updated" 35 | 36 | @BeforeTest 37 | fun before() { 38 | every { subjectEmitter.emit(ExampleQuery::class.java, match { it.test(exampleQuery) }, exampleUpdatePayload) } returns Unit 39 | } 40 | 41 | @Test 42 | fun `Emit extension should invoke correct emit method with a class parameter`() { 43 | subjectEmitter.emit(exampleUpdatePayload) { it.value == exampleQuery.value } 44 | verify { subjectEmitter.emit(ExampleQuery::class.java, match { it.test(exampleQuery) }, exampleUpdatePayload) } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/ResultDiscriminatorCommandCallbackTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import io.mockk.every 19 | import io.mockk.mockk 20 | import io.mockk.verify 21 | import org.axonframework.commandhandling.CommandMessage 22 | import org.axonframework.commandhandling.GenericCommandMessage 23 | import org.axonframework.commandhandling.GenericCommandResultMessage 24 | import org.axonframework.commandhandling.distributed.CommandDispatchException 25 | import org.axonframework.messaging.MetaData 26 | import java.util.* 27 | import kotlin.test.Test 28 | import kotlin.test.fail 29 | 30 | /** 31 | * Tests [ResultDiscriminatorCommandCallback] to verify that it correctly invokes given callbacks. 32 | * 33 | * @author Stefan Andjelkovic 34 | */ 35 | internal class ResultDiscriminatorCommandCallbackTest { 36 | private val command = ExampleCommand("1") 37 | private val commandMessage: CommandMessage = GenericCommandMessage.asCommandMessage(command) 38 | private val responsePayloadUUID = UUID.randomUUID().toString() 39 | private val commandResultMessage = GenericCommandResultMessage.asCommandResultMessage(responsePayloadUUID) 40 | private val exceptionalCommandResultMessage = GenericCommandResultMessage.asCommandResultMessage(CommandDispatchException("Exception message")) 41 | private val metaData = MetaData.with("key", "value") 42 | 43 | @Test 44 | fun `Should invoke onResult for successful response`() { 45 | val onSuccessMock = mockk<(commandMessage: CommandMessage, result: String, metaData: MetaData) -> Unit>() 46 | every { onSuccessMock.invoke(commandMessage, responsePayloadUUID, any()) } returns Unit 47 | 48 | val subject = ResultDiscriminatorCommandCallback( 49 | onError = { _, _, _ -> fail("onError should not be called") }, 50 | onSuccess = onSuccessMock 51 | ) 52 | 53 | subject.onResult(commandMessage, commandResultMessage.withMetaData(metaData)) 54 | 55 | verify { subject.onSuccess.invoke(commandMessage, responsePayloadUUID, metaData) } 56 | } 57 | 58 | @Test 59 | fun `Should invoke onResult for successful response and provide default metadata`() { 60 | val onSuccessMock = mockk<(commandMessage: CommandMessage, result: String, metaData: MetaData) -> Unit>() 61 | every { onSuccessMock.invoke(commandMessage, responsePayloadUUID, any()) } returns Unit 62 | 63 | val subject = ResultDiscriminatorCommandCallback( 64 | onError = { _, _, _ -> fail("onError should not be called") }, 65 | onSuccess = onSuccessMock 66 | ) 67 | 68 | subject.onResult(commandMessage, commandResultMessage) 69 | 70 | verify { subject.onSuccess.invoke(commandMessage, responsePayloadUUID, MetaData.emptyInstance()) } 71 | } 72 | 73 | @Test 74 | fun `Should invoke onResult for exceptional response`() { 75 | val onError = mockk<(commandMessage: CommandMessage, exception: Throwable, metaData: MetaData) -> Unit>() 76 | every { onError.invoke(commandMessage, exceptionalCommandResultMessage.exceptionResult(), any()) } returns Unit 77 | 78 | val subject = ResultDiscriminatorCommandCallback( 79 | onError = onError, 80 | onSuccess = { _, _, _ -> fail("onSuccess should not be called") } 81 | ) 82 | 83 | subject.onResult(commandMessage, exceptionalCommandResultMessage.withMetaData(metaData)) 84 | 85 | verify { subject.onError.invoke(commandMessage, exceptionalCommandResultMessage.exceptionResult(), metaData) } 86 | } 87 | 88 | 89 | @Test 90 | fun `Should invoke onResult for exceptional response and provide default metadata`() { 91 | val onError = mockk<(commandMessage: CommandMessage, exception: Throwable, metaData: MetaData) -> Unit>() 92 | every { onError.invoke(commandMessage, exceptionalCommandResultMessage.exceptionResult(), any()) } returns Unit 93 | 94 | val subject = ResultDiscriminatorCommandCallback( 95 | onError = onError, 96 | onSuccess = { _, _, _ -> fail("onSuccess should not be called") } 97 | ) 98 | 99 | subject.onResult(commandMessage, exceptionalCommandResultMessage) 100 | 101 | verify { subject.onError.invoke(commandMessage, exceptionalCommandResultMessage.exceptionResult(), MetaData.emptyInstance()) } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/mockkExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2021. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin 17 | 18 | import io.mockk.MockKMatcherScope 19 | import io.mockk.MockKVerificationScope 20 | import org.axonframework.messaging.responsetypes.AbstractResponseType 21 | import org.axonframework.messaging.responsetypes.InstanceResponseType 22 | import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType 23 | import org.axonframework.messaging.responsetypes.OptionalResponseType 24 | import org.axonframework.messaging.responsetypes.ResponseType 25 | import java.util.* 26 | 27 | /** 28 | * Matches wrapped type of given [ResponseType] instead of entire [ResponseType] object 29 | * @param clazz Class object of the type to match 30 | * @param T Type wrapped by [ResponseType] 31 | */ 32 | internal fun MockKVerificationScope.matchExpectedResponseType(clazz: Class) = match { type: ResponseType -> type.expectedResponseType == clazz } 33 | 34 | /** 35 | * Matches that given [ResponseType] is [InstanceResponseType] 36 | * @param T Type wrapped by given [ResponseType] instance. Required for Mockk but will not be explicitly matched 37 | */ 38 | internal fun MockKMatcherScope.matchInstanceResponseType() = match { type: AbstractResponseType -> type is InstanceResponseType } 39 | 40 | /** 41 | * Matches that given [ResponseType] is [OptionalResponseType]. Will not check type wrapped by the [Optional] instance 42 | * @param T Type wrapped by given [ResponseType] instance. Required for Mockk but will not be explicitly matched 43 | */ 44 | internal fun MockKMatcherScope.matchOptionalResponseType() = match { type: AbstractResponseType> -> type is OptionalResponseType } 45 | 46 | /** 47 | * Matches that given [ResponseType] is [MultipleInstancesResponseType]. Will not check type wrapped by the [List] instance 48 | * @param T Type wrapped by given [ResponseType] instance. Required for Mockk but will not be explicitly matched 49 | */ 50 | internal fun MockKMatcherScope.matchMultipleInstancesResponseType() = match { type: AbstractResponseType> -> type is MultipleInstancesResponseType<*> } 51 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/AxonSerializersTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | import kotlinx.serialization.Serializable 19 | import kotlinx.serialization.encodeToString 20 | import kotlinx.serialization.json.Json 21 | import org.axonframework.eventhandling.* 22 | import org.axonframework.eventhandling.scheduling.ScheduleToken 23 | import org.axonframework.eventhandling.scheduling.java.SimpleScheduleToken 24 | import org.axonframework.eventhandling.scheduling.quartz.QuartzScheduleToken 25 | import org.axonframework.eventhandling.tokenstore.ConfigToken 26 | import org.axonframework.extensions.kotlin.messaging.responsetypes.ArrayResponseType 27 | import org.axonframework.extensions.kotlin.serialization.AxonSerializersModule 28 | import org.axonframework.extensions.kotlin.serialization.KotlinSerializer 29 | import org.axonframework.messaging.MetaData 30 | import org.axonframework.messaging.responsetypes.InstanceResponseType 31 | import org.axonframework.messaging.responsetypes.MultipleInstancesResponseType 32 | import org.axonframework.messaging.responsetypes.OptionalResponseType 33 | import org.axonframework.messaging.responsetypes.ResponseType 34 | import org.axonframework.serialization.Serializer 35 | import org.axonframework.serialization.SimpleSerializedObject 36 | import org.axonframework.serialization.SimpleSerializedType 37 | import org.junit.jupiter.api.Assertions.assertEquals 38 | import org.junit.jupiter.api.Assertions.assertInstanceOf 39 | import org.junit.jupiter.api.Nested 40 | import org.junit.jupiter.api.Test 41 | 42 | internal class AxonSerializersTest { 43 | 44 | private val serializer = KotlinSerializer(Json { serializersModule = AxonSerializersModule }) 45 | 46 | @Test 47 | fun configToken() { 48 | val token = ConfigToken(mapOf("important-property" to "important-value", "other-property" to "other-value")) 49 | val json = """{"config":{"important-property":"important-value","other-property":"other-value"}}""" 50 | assertEquals(json, serializer.serialize(token, String::class.java).data) 51 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 52 | } 53 | 54 | @Test 55 | fun gapAwareTrackingToken() { 56 | val token = GapAwareTrackingToken(10, setOf(7, 8, 9)) 57 | val json = """{"index":10,"gaps":[7,8,9]}""" 58 | assertEquals(json, serializer.serialize(token, String::class.java).data) 59 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 60 | } 61 | 62 | @Test 63 | fun multiSourceTrackingToken() { 64 | val token = MultiSourceTrackingToken(mapOf("primary" to GlobalSequenceTrackingToken(5), "secondary" to GlobalSequenceTrackingToken(10))) 65 | val json = """{"trackingTokens":{"primary":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"secondary":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10}}}""" 66 | assertEquals(json, serializer.serialize(token, String::class.java).data) 67 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 68 | } 69 | 70 | @Test 71 | fun mergedTrackingToken() { 72 | val token = MergedTrackingToken(GlobalSequenceTrackingToken(5), GlobalSequenceTrackingToken(10)) 73 | val json = """{"lowerSegmentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"upperSegmentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10}}""" 74 | assertEquals(json, serializer.serialize(token, String::class.java).data) 75 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 76 | } 77 | 78 | @Test 79 | fun `replay token with String context`() { 80 | val token = ReplayToken.createReplayToken( 81 | GlobalSequenceTrackingToken(15), GlobalSequenceTrackingToken(10), "someContext" 82 | ) 83 | val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":15},"currentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10},"context":"someContext"}""".trimIndent() 84 | assertEquals(json, serializer.serialize(token, String::class.java).data) 85 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 86 | } 87 | 88 | @Test 89 | fun `replay token with currentToken with null value and null context`() { 90 | val token = ReplayToken.createReplayToken(GlobalSequenceTrackingToken(5), null, null) 91 | val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"currentToken":null,"context":null}""" 92 | assertEquals(json, serializer.serialize(token, String::class.java).data) 93 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 94 | } 95 | 96 | @Test 97 | fun `replay token deserialize without context field`() { 98 | val token = ReplayToken.createReplayToken(GlobalSequenceTrackingToken(5), null, null) 99 | val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":5},"currentToken":null}""" 100 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 101 | } 102 | 103 | @Test 104 | fun `replay token with complex object as String context`() { 105 | @Serializable 106 | data class ComplexContext(val value1: String, val value2: Int, val value3: Boolean) 107 | val complexContext = ComplexContext("value1", 2, false) 108 | 109 | val token = ReplayToken.createReplayToken( 110 | GlobalSequenceTrackingToken(15), 111 | GlobalSequenceTrackingToken(10), 112 | Json.encodeToString(complexContext) 113 | ) 114 | val json = """{"tokenAtReset":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":15},"currentToken":{"type":"org.axonframework.eventhandling.GlobalSequenceTrackingToken","globalIndex":10},"context":"{\"value1\":\"value1\",\"value2\":2,\"value3\":false}"}""".trimIndent() 115 | assertEquals(json, serializer.serialize(token, String::class.java).data) 116 | val deserializedToken = serializer.deserializeTrackingToken(token.javaClass.name, json) as ReplayToken 117 | assertEquals(token, deserializedToken) 118 | assertInstanceOf(String::class.java, deserializedToken.context()) 119 | assertEquals(complexContext, Json.decodeFromString(deserializedToken.context() as String)) 120 | } 121 | 122 | @Test 123 | fun globalSequenceTrackingToken() { 124 | val token = GlobalSequenceTrackingToken(5) 125 | val json = """{"globalIndex":5}""" 126 | assertEquals(json, serializer.serialize(token, String::class.java).data) 127 | assertEquals(token, serializer.deserializeTrackingToken(token.javaClass.name, json)) 128 | } 129 | 130 | @Test 131 | fun simpleScheduleToken() { 132 | val token = SimpleScheduleToken("my-token-id") 133 | val json = """{"tokenId":"my-token-id"}""" 134 | assertEquals(json, serializer.serialize(token, String::class.java).data) 135 | assertEquals(token, serializer.deserializeScheduleToken(token.javaClass.name, json)) 136 | } 137 | 138 | @Test 139 | fun quartzScheduleToken() { 140 | val token = QuartzScheduleToken("my-job-id", "my-group-id") 141 | val json = """{"jobIdentifier":"my-job-id","groupIdentifier":"my-group-id"}""" 142 | assertEquals(json, serializer.serialize(token, String::class.java).data) 143 | assertEquals(token, serializer.deserializeScheduleToken(token.javaClass.name, json)) 144 | } 145 | 146 | @Test 147 | fun instanceResponseType() { 148 | val responseType = InstanceResponseType(String::class.java) 149 | val json = """{"expectedResponseType":"java.lang.String"}""" 150 | assertEquals(json, serializer.serialize(responseType, String::class.java).data) 151 | assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) 152 | } 153 | 154 | @Test 155 | fun optionalResponseType() { 156 | val responseType = OptionalResponseType(String::class.java) 157 | val json = """{"expectedResponseType":"java.lang.String"}""" 158 | assertEquals(json, serializer.serialize(responseType, String::class.java).data) 159 | assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) 160 | } 161 | 162 | @Test 163 | fun multipleInstancesResponseType() { 164 | val responseType = MultipleInstancesResponseType(String::class.java) 165 | val json = """{"expectedResponseType":"java.lang.String"}""" 166 | assertEquals(json, serializer.serialize(responseType, String::class.java).data) 167 | assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) 168 | } 169 | 170 | @Test 171 | fun arrayResponseType() { 172 | val responseType = ArrayResponseType(String::class.java) 173 | val json = """{"expectedResponseType":"java.lang.String"}""" 174 | assertEquals(json, serializer.serialize(responseType, String::class.java).data) 175 | assertEquals(responseType, serializer.deserializeResponseType(responseType.javaClass.name, json)) 176 | } 177 | 178 | private fun Serializer.deserializeTrackingToken(tokenType: String, json: String): TrackingToken = 179 | deserializeJson(tokenType, json) 180 | 181 | private fun Serializer.deserializeScheduleToken(tokenType: String, json: String): ScheduleToken = 182 | deserializeJson(tokenType, json) 183 | 184 | private fun Serializer.deserializeResponseType(responseType: String, json: String): ResponseType<*> = 185 | deserializeJson(responseType, json) 186 | 187 | private fun Serializer.deserializeJson(type: String, json: String): T { 188 | val serializedType = SimpleSerializedType(type, null) 189 | val serializedToken = SimpleSerializedObject(json, String::class.java, serializedType) 190 | return deserialize(serializedToken) 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerCborTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | import kotlinx.serialization.ExperimentalSerializationApi 19 | import kotlinx.serialization.cbor.Cbor 20 | import org.axonframework.extensions.kotlin.serialization.KotlinSerializer 21 | import org.junit.jupiter.api.Assertions.assertEquals 22 | import org.junit.jupiter.api.Test 23 | 24 | class KotlinSerializerCborTest { 25 | @OptIn(ExperimentalSerializationApi::class) 26 | private val sut = KotlinSerializer(Cbor) 27 | 28 | @Test 29 | fun testStandardList() { 30 | val items = listOf( 31 | TypeOne("a", 1), 32 | TypeOne("b", 2), 33 | ) 34 | val actual = sut.serialize(items, ByteArray::class.java) 35 | val expected = "9fbf646e616d65616163666f6f01ffbf646e616d65616263666f6f02ffff" 36 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) 37 | assertEquals(expected, actual.data.toHex()) 38 | } 39 | 40 | @Test 41 | fun testPolymorphicList() { 42 | val items = listOf( 43 | TypeOne("a", 1), 44 | TypeOne("b", 2), 45 | TypeTwo("c", listOf(3, 4)), 46 | TypeOne("d", 5), 47 | ) 48 | val actual = sut.serialize(items, ByteArray::class.java) 49 | val expected = "9f9f636f6e65bf646e616d65616163666f6f01ffff9f636f6e65bf646e616d65616263666f6f02ffff9f6374776fbf646e616d656163636261729f0304ffffff9f636f6e65bf646e616d65616463666f6f05ffffff" 50 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) 51 | assertEquals(expected, actual.data.toHex()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerJsonTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | import kotlinx.serialization.json.Json 19 | import org.axonframework.extensions.kotlin.serialization.KotlinSerializer 20 | import org.junit.jupiter.api.Assertions.assertEquals 21 | import org.junit.jupiter.api.Test 22 | 23 | class KotlinSerializerJsonTest { 24 | private val sut = KotlinSerializer(Json) 25 | 26 | @Test 27 | fun testStandardList() { 28 | val items = listOf( 29 | TypeOne("a", 1), 30 | TypeOne("b", 2), 31 | ) 32 | val actual = sut.serialize(items, String::class.java) 33 | val expected = """[{"name":"a","foo":1},{"name":"b","foo":2}]""" 34 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) 35 | assertEquals(expected, actual.data) 36 | } 37 | 38 | @Test 39 | fun testPolymorphicList() { 40 | val items = listOf( 41 | TypeOne("a", 1), 42 | TypeOne("b", 2), 43 | TypeTwo("c", listOf(3, 4)), 44 | TypeOne("d", 5), 45 | ) 46 | val actual = sut.serialize(items, String::class.java) 47 | val expected = """[{"type":"one","name":"a","foo":1},{"type":"one","name":"b","foo":2},{"type":"two","name":"c","bar":[3,4]},{"type":"one","name":"d","foo":5}]""" 48 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) 49 | assertEquals(expected, actual.data) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/KotlinSerializerProtobufTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | import kotlinx.serialization.ExperimentalSerializationApi 19 | import kotlinx.serialization.protobuf.ProtoBuf 20 | import org.axonframework.extensions.kotlin.serialization.KotlinSerializer 21 | import org.junit.jupiter.api.Assertions.assertEquals 22 | import org.junit.jupiter.api.Test 23 | 24 | class KotlinSerializerProtobufTest { 25 | @OptIn(ExperimentalSerializationApi::class) 26 | private val sut = KotlinSerializer(ProtoBuf) 27 | 28 | @Test 29 | fun testStandardList() { 30 | val items = listOf( 31 | TypeOne("a", 1), 32 | TypeOne("b", 2), 33 | ) 34 | val actual = sut.serialize(items, ByteArray::class.java) 35 | val expected = "02050a01611001050a01621002" 36 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.TypeOne", actual.type.name) 37 | assertEquals(expected, actual.data.toHex()) 38 | } 39 | 40 | @Test 41 | fun testPolymorphicList() { 42 | val items = listOf( 43 | TypeOne("a", 1), 44 | TypeOne("b", 2), 45 | TypeTwo("c", listOf(3, 4)), 46 | TypeOne("d", 5), 47 | ) 48 | val actual = sut.serialize(items, ByteArray::class.java) 49 | val expected = "040c0a036f6e6512050a016110010c0a036f6e6512050a016210020e0a0374776f12070a0163100310040c0a036f6e6512050a01641005" 50 | assertEquals("List:org.axonframework.extensions.kotlin.serializer.SuperType", actual.type.name) 51 | assertEquals(expected, actual.data.toHex()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/MetaDataSerializerTest.kt: -------------------------------------------------------------------------------- 1 | package org.axonframework.extensions.kotlin.serializer 2 | 3 | import kotlinx.serialization.ExperimentalSerializationApi 4 | import kotlinx.serialization.cbor.Cbor 5 | import kotlinx.serialization.json.Json 6 | import org.axonframework.extensions.kotlin.serialization.AxonSerializersModule 7 | import org.axonframework.extensions.kotlin.serialization.KotlinSerializer 8 | import org.axonframework.messaging.MetaData 9 | import org.axonframework.serialization.SerializationException 10 | import org.axonframework.serialization.SimpleSerializedObject 11 | import org.axonframework.serialization.SimpleSerializedType 12 | import org.junit.jupiter.api.Assertions.* 13 | import org.junit.jupiter.api.Test 14 | import org.junit.jupiter.api.assertThrows 15 | import java.time.Instant 16 | import java.util.* 17 | 18 | class MetaDataSerializerTest { 19 | 20 | private val jsonSerializer = KotlinSerializer( 21 | serialFormat = Json { 22 | serializersModule = AxonSerializersModule 23 | } 24 | ) 25 | 26 | @OptIn(ExperimentalSerializationApi::class) 27 | private val cborSerializer = KotlinSerializer( 28 | serialFormat = Cbor { 29 | serializersModule = AxonSerializersModule 30 | } 31 | ) 32 | 33 | @Test 34 | fun `should serialize and deserialize empty MetaData`() { 35 | val emptyMetaData = MetaData.emptyInstance() 36 | 37 | val serialized = jsonSerializer.serialize(emptyMetaData, String::class.java) 38 | val deserialized = jsonSerializer.deserialize(serialized) 39 | 40 | assertEquals(emptyMetaData, deserialized) 41 | assertTrue(deserialized!!.isEmpty()) 42 | } 43 | 44 | @Test 45 | fun `should serialize and deserialize MetaData with String values`() { 46 | val metaData = MetaData.with("key1", "value1") 47 | .and("key2", "value2") 48 | 49 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 50 | val deserialized = jsonSerializer.deserialize(serialized) 51 | 52 | assertEquals(metaData, deserialized) 53 | assertEquals("value1", deserialized?.get("key1")) 54 | assertEquals("value2", deserialized?.get("key2")) 55 | } 56 | 57 | @Test 58 | fun `should serialize and deserialize MetaData with numeric values`() { 59 | val metaData = MetaData.with("int", 42) 60 | .and("long", 123456789L) 61 | .and("double", 3.14159) 62 | 63 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 64 | val deserialized = jsonSerializer.deserialize(serialized) 65 | 66 | assertEquals(metaData.size, deserialized!!.size) 67 | // Note: numbers might be deserialized as different numeric types 68 | // but their string representation should match 69 | assertEquals(metaData["int"].toString(), deserialized.get("int").toString()) 70 | assertEquals(metaData["long"].toString(), deserialized.get("long").toString()) 71 | assertEquals(metaData["double"].toString(), deserialized.get("double").toString()) 72 | } 73 | 74 | @Test 75 | fun `should serialize and deserialize MetaData with boolean values`() { 76 | val metaData = MetaData.with("isTrue", true) 77 | .and("isFalse", false) 78 | .and("isFalseString", "false") 79 | 80 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 81 | val deserialized = jsonSerializer.deserialize(serialized) 82 | 83 | assertEquals(metaData, deserialized) 84 | assertEquals(true, deserialized?.get("isTrue")) 85 | assertEquals(false, deserialized?.get("isFalse")) 86 | assertEquals("false", deserialized?.get("isFalseString")) 87 | } 88 | 89 | @Test 90 | fun `should serialize and deserialize MetaData with null values`() { 91 | val metaData = MetaData.with("nullValue", null) 92 | .and("nonNullValue", "present") 93 | 94 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 95 | val deserialized = jsonSerializer.deserialize(serialized) 96 | 97 | assertEquals(metaData, deserialized) 98 | assertNull(deserialized?.get("nullValue")) 99 | assertEquals("present", deserialized?.get("nonNullValue")) 100 | } 101 | 102 | @Test 103 | fun `should handle UUID`() { 104 | val uuid = UUID.randomUUID() 105 | val metaData = MetaData.with("uuid", uuid) 106 | 107 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 108 | val deserialized = jsonSerializer.deserialize(serialized) 109 | 110 | assertEquals(uuid, metaData.get("uuid")) 111 | } 112 | 113 | @Test 114 | fun `should handle Instant as String representation`() { 115 | val timestamp = Instant.now() 116 | 117 | val metaData = MetaData.with("timestamp", timestamp) 118 | 119 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 120 | val deserialized = jsonSerializer.deserialize(serialized)!! 121 | 122 | assertEquals(timestamp.toString(), deserialized.get("timestamp")) 123 | } 124 | 125 | @Test 126 | fun `should work with mixed value types`() { 127 | val metaData = MetaData.with("string", "text") 128 | .and("number", 123) 129 | .and("boolean", true) 130 | .and("null", null) 131 | 132 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 133 | val deserialized = jsonSerializer.deserialize(serialized) 134 | 135 | assertEquals(metaData, deserialized) 136 | } 137 | 138 | @Test 139 | fun `should work with CBOR format`() { 140 | val metaData = MetaData.with("string", "text") 141 | .and("number", 123) 142 | .and("boolean", true) 143 | 144 | val serialized = cborSerializer.serialize(metaData, ByteArray::class.java) 145 | val deserialized = cborSerializer.deserialize(serialized) 146 | 147 | assertEquals(metaData, deserialized) 148 | } 149 | 150 | @Test 151 | fun `should handle string that looks like a number or boolean`() { 152 | val metaData = MetaData.with("numberString", "123") 153 | .and("booleanString", "true") 154 | 155 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 156 | val deserialized = jsonSerializer.deserialize(serialized) 157 | 158 | assertEquals(metaData, deserialized) 159 | } 160 | 161 | @Test 162 | fun `should handle nested maps in MetaData`() { 163 | val nestedMap = mapOf( 164 | "key1" to "value1", 165 | "key2" to 123, 166 | "nested" to mapOf("a" to 1, "b" to 2) 167 | ) 168 | 169 | val metaData = MetaData.with("mapValue", nestedMap) 170 | 171 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 172 | val deserialized = jsonSerializer.deserialize(serialized) 173 | 174 | val deserializedValue = deserialized!!["mapValue"] 175 | assertEquals(nestedMap, deserializedValue) 176 | } 177 | 178 | @Test 179 | fun `should handle lists in MetaData`() { 180 | val list = listOf("item1", "item2", "item3") 181 | 182 | val metaData = MetaData.with("listValue", list) 183 | 184 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 185 | val deserialized = jsonSerializer.deserialize(serialized) 186 | 187 | assertEquals(metaData.size, deserialized!!.size) 188 | 189 | val deserializedValue = deserialized["listValue"] as List<*> 190 | assertTrue(deserializedValue.contains("item1")) 191 | assertTrue(deserializedValue.contains("item2")) 192 | assertTrue(deserializedValue.contains("item3")) 193 | } 194 | 195 | @Test 196 | fun `should handle complex nested structures in MetaData`() { 197 | val complexStructure = mapOf( 198 | "string" to "value", 199 | "number" to 42, 200 | "boolean" to true, 201 | "null" to null, 202 | "list" to listOf(1, 2, 3), 203 | "nestedMap" to mapOf( 204 | "a" to "valueA", 205 | "b" to listOf("x", "y", "z"), 206 | "c" to mapOf("nested" to "deepValue") 207 | ) 208 | ) 209 | 210 | val metaData = MetaData.with("complexValue", complexStructure) 211 | 212 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 213 | val deserialized = jsonSerializer.deserialize(serialized) 214 | 215 | assertEquals(metaData.size, deserialized!!.size) 216 | 217 | val deserializedValue = deserialized["complexValue"] as Map<*, *> 218 | assertEquals(deserializedValue, complexStructure) 219 | } 220 | 221 | @Test 222 | fun `should not escape quotes in complex nested structures in MetaData`() { 223 | val complexStructure = mapOf( 224 | "string" to "value", 225 | "number" to 42, 226 | "boolean" to true, 227 | "null" to null, 228 | "list" to listOf(1, 2, 3), 229 | "nestedMap" to mapOf( 230 | "a" to "valueA", 231 | "b" to listOf("x", "y", "z"), 232 | "c" to mapOf("nested" to "deepValue") 233 | ) 234 | ) 235 | 236 | val metaData = MetaData.with("complexValue", complexStructure) 237 | 238 | val serialized = jsonSerializer.serialize(metaData, String::class.java) 239 | val json = """{"complexValue":{"string":"value","number":42,"boolean":true,"null":null,"list":[1,2,3],"nestedMap":{"a":"valueA","b":["x","y","z"],"c":{"nested":"deepValue"}}}}""" 240 | assertEquals(json, serialized.data); 241 | } 242 | 243 | @Test 244 | fun `do not handle custom objects`() { 245 | data class Person(val name: String, val age: Int) 246 | 247 | val person = Person("John Doe", 30) 248 | 249 | val metaData = MetaData.with("personValue", person) 250 | 251 | assertThrows { 252 | jsonSerializer.serialize(metaData, String::class.java) 253 | } 254 | } 255 | 256 | @Test 257 | fun `should throw exception when deserializing malformed JSON`() { 258 | val serializedType = SimpleSerializedType(MetaData::class.java.name, null) 259 | 260 | val syntaxErrorJson = """{"key": value""" // missing closing bracket around value 261 | val syntaxErrorObject = SimpleSerializedObject(syntaxErrorJson, String::class.java, serializedType) 262 | 263 | assertThrows { 264 | jsonSerializer.deserialize(syntaxErrorObject) 265 | } 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/hexExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | // copied from https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings#1-formatter 19 | fun ByteArray.toHex(): String = 20 | joinToString("") { eachByte -> "%02x".format(eachByte) } -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/serializer/testTypes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2024. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.axonframework.extensions.kotlin.serializer 17 | 18 | import kotlinx.serialization.SerialName 19 | import kotlinx.serialization.Serializable 20 | 21 | @Serializable 22 | sealed interface SuperType { 23 | val name: String 24 | } 25 | 26 | @Serializable 27 | @SerialName("one") 28 | data class TypeOne( 29 | override val name: String, 30 | val foo: Int, 31 | ) : SuperType 32 | 33 | @Serializable 34 | @SerialName("two") 35 | data class TypeTwo( 36 | override val name: String, 37 | val bar: List, 38 | ) : SuperType 39 | -------------------------------------------------------------------------------- /kotlin/src/test/kotlin/org/axonframework/extensions/kotlin/testObjects.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2023. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.axonframework.extensions.kotlin 18 | 19 | import org.axonframework.modelling.command.TargetAggregateIdentifier 20 | import org.axonframework.queryhandling.SubscriptionQueryResult 21 | import reactor.core.publisher.Flux 22 | import reactor.core.publisher.Mono 23 | 24 | /** 25 | * Simple Query class to be used in tests. 26 | */ 27 | internal data class ExampleQuery(val value: Number) 28 | 29 | /** 30 | * Simple Command class to be used in tests. 31 | */ 32 | internal data class ExampleCommand(@TargetAggregateIdentifier val id: String) 33 | 34 | /** 35 | * Class used as update response type in subscriptionQuery method. 36 | */ 37 | internal data class UpdateResponseType(val dummy: String) 38 | 39 | /** 40 | * Class used as initial response type in subscriptionQuery method. 41 | */ 42 | internal data class InitialResponseType(val dummy: String) 43 | 44 | /** 45 | * Dummy class used as return object from subscriptionQuery method in the mock. 46 | */ 47 | internal class ExampleSubscriptionQueryResult : SubscriptionQueryResult { 48 | override fun cancel(): Boolean { 49 | TODO("Not yet implemented") 50 | } 51 | 52 | override fun initialResult(): Mono { 53 | TODO("Not yet implemented") 54 | } 55 | 56 | override fun updates(): Flux { 57 | TODO("Not yet implemented") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2010-2024. Axon Framework 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # ---------------------------------------------------------------------------- 19 | # Apache Maven Wrapper startup batch script, version 3.2.0 20 | # 21 | # Required ENV vars: 22 | # ------------------ 23 | # JAVA_HOME - location of a JDK home dir 24 | # 25 | # Optional ENV vars 26 | # ----------------- 27 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 28 | # e.g. to debug Maven itself, use 29 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 30 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 31 | # ---------------------------------------------------------------------------- 32 | 33 | if [ -z "$MAVEN_SKIP_RC" ] ; then 34 | 35 | if [ -f /usr/local/etc/mavenrc ] ; then 36 | . /usr/local/etc/mavenrc 37 | fi 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "$(uname)" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 62 | else 63 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=$(java-config --jre-home) 72 | fi 73 | fi 74 | 75 | # For Cygwin, ensure paths are in UNIX format before anything is touched 76 | if $cygwin ; then 77 | [ -n "$JAVA_HOME" ] && 78 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 79 | [ -n "$CLASSPATH" ] && 80 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 81 | fi 82 | 83 | # For Mingw, ensure paths are in UNIX format before anything is touched 84 | if $mingw ; then 85 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 86 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 87 | fi 88 | 89 | if [ -z "$JAVA_HOME" ]; then 90 | javaExecutable="$(which javac)" 91 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 92 | # readlink(1) is not available as standard on Solaris 10. 93 | readLink=$(which readlink) 94 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 95 | if $darwin ; then 96 | javaHome="$(dirname "\"$javaExecutable\"")" 97 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 98 | else 99 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 100 | fi 101 | javaHome="$(dirname "\"$javaExecutable\"")" 102 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 103 | JAVA_HOME="$javaHome" 104 | export JAVA_HOME 105 | fi 106 | fi 107 | fi 108 | 109 | if [ -z "$JAVACMD" ] ; then 110 | if [ -n "$JAVA_HOME" ] ; then 111 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 112 | # IBM's JDK on AIX uses strange locations for the executables 113 | JAVACMD="$JAVA_HOME/jre/sh/java" 114 | else 115 | JAVACMD="$JAVA_HOME/bin/java" 116 | fi 117 | else 118 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 119 | fi 120 | fi 121 | 122 | if [ ! -x "$JAVACMD" ] ; then 123 | echo "Error: JAVA_HOME is not defined correctly." >&2 124 | echo " We cannot execute $JAVACMD" >&2 125 | exit 1 126 | fi 127 | 128 | if [ -z "$JAVA_HOME" ] ; then 129 | echo "Warning: JAVA_HOME environment variable is not set." 130 | fi 131 | 132 | # traverses directory structure from process work directory to filesystem root 133 | # first directory with .mvn subdirectory is considered project base directory 134 | find_maven_basedir() { 135 | if [ -z "$1" ] 136 | then 137 | echo "Path not specified to find_maven_basedir" 138 | return 1 139 | fi 140 | 141 | basedir="$1" 142 | wdir="$1" 143 | while [ "$wdir" != '/' ] ; do 144 | if [ -d "$wdir"/.mvn ] ; then 145 | basedir=$wdir 146 | break 147 | fi 148 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 149 | if [ -d "${wdir}" ]; then 150 | wdir=$(cd "$wdir/.." || exit 1; pwd) 151 | fi 152 | # end of workaround 153 | done 154 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 155 | } 156 | 157 | # concatenates all lines of a file 158 | concat_lines() { 159 | if [ -f "$1" ]; then 160 | # Remove \r in case we run on Windows within Git Bash 161 | # and check out the repository with auto CRLF management 162 | # enabled. Otherwise, we may read lines that are delimited with 163 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 164 | # splitting rules. 165 | tr -s '\r\n' ' ' < "$1" 166 | fi 167 | } 168 | 169 | log() { 170 | if [ "$MVNW_VERBOSE" = true ]; then 171 | printf '%s\n' "$1" 172 | fi 173 | } 174 | 175 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 176 | if [ -z "$BASE_DIR" ]; then 177 | exit 1; 178 | fi 179 | 180 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 181 | log "$MAVEN_PROJECTBASEDIR" 182 | 183 | ########################################################################################## 184 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 185 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 186 | ########################################################################################## 187 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 188 | if [ -r "$wrapperJarPath" ]; then 189 | log "Found $wrapperJarPath" 190 | else 191 | log "Couldn't find $wrapperJarPath, downloading it ..." 192 | 193 | if [ -n "$MVNW_REPOURL" ]; then 194 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 195 | else 196 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 197 | fi 198 | while IFS="=" read -r key value; do 199 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 200 | safeValue=$(echo "$value" | tr -d '\r') 201 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 202 | esac 203 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 204 | log "Downloading from: $wrapperUrl" 205 | 206 | if $cygwin; then 207 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 208 | fi 209 | 210 | if command -v wget > /dev/null; then 211 | log "Found wget ... using wget" 212 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 213 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 214 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 215 | else 216 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 217 | fi 218 | elif command -v curl > /dev/null; then 219 | log "Found curl ... using curl" 220 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 221 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 222 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 223 | else 224 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 225 | fi 226 | else 227 | log "Falling back to using Java to download" 228 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 229 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 230 | # For Cygwin, switch paths to Windows format before running javac 231 | if $cygwin; then 232 | javaSource=$(cygpath --path --windows "$javaSource") 233 | javaClass=$(cygpath --path --windows "$javaClass") 234 | fi 235 | if [ -e "$javaSource" ]; then 236 | if [ ! -e "$javaClass" ]; then 237 | log " - Compiling MavenWrapperDownloader.java ..." 238 | ("$JAVA_HOME/bin/javac" "$javaSource") 239 | fi 240 | if [ -e "$javaClass" ]; then 241 | log " - Running MavenWrapperDownloader.java ..." 242 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 243 | fi 244 | fi 245 | fi 246 | fi 247 | ########################################################################################## 248 | # End of extension 249 | ########################################################################################## 250 | 251 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 252 | wrapperSha256Sum="" 253 | while IFS="=" read -r key value; do 254 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 255 | esac 256 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 257 | if [ -n "$wrapperSha256Sum" ]; then 258 | wrapperSha256Result=false 259 | if command -v sha256sum > /dev/null; then 260 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 261 | wrapperSha256Result=true 262 | fi 263 | elif command -v shasum > /dev/null; then 264 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 265 | wrapperSha256Result=true 266 | fi 267 | else 268 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 269 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 270 | exit 1 271 | fi 272 | if [ $wrapperSha256Result = false ]; then 273 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 274 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 275 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 276 | exit 1 277 | fi 278 | fi 279 | 280 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 281 | 282 | # For Cygwin, switch paths to Windows format before running java 283 | if $cygwin; then 284 | [ -n "$JAVA_HOME" ] && 285 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 286 | [ -n "$CLASSPATH" ] && 287 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 288 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 289 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 290 | fi 291 | 292 | # Provide a "standardized" way to retrieve the CLI args that will 293 | # work with both Windows and non-Windows executions. 294 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 295 | export MAVEN_CMD_LINE_ARGS 296 | 297 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 298 | 299 | # shellcheck disable=SC2086 # safe args 300 | exec "$JAVACMD" \ 301 | $MAVEN_OPTS \ 302 | $MAVEN_DEBUG_OPTS \ 303 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 304 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 305 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 306 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | --------------------------------------------------------------------------------