├── .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 | [](https://maven-badges.herokuapp.com/maven-central/org.axonframework.extensions.kotlin/axon-kotlin)
3 | 
4 | [](https://sonarcloud.io/dashboard?id=AxonFramework_extension-kotlin)
5 | [](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 |
--------------------------------------------------------------------------------