From a254ca107af3e1929d6479ebfb566e53152286e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 17:16:57 +0200 Subject: [PATCH] Release version 0.4.0 (#381) * Generate OpenApi Spec * feat(baseImage): replace alpine with temurin as base image for running java application * Lint and refactor mostly all *.md files * Lint new changes from develop branch * Replace appearance of product-edc with tractusx-edc * Fix README.md and Transfer Data.md * Fix Transfer Data.md * Regenerate helm chart README.md files * Remove left over html tags from root REAMDE.md * Add empty line at EOF * Update CODE_OF_CONDUCT.md * Retrigger ci * Release: fix version handling * Prepare release 0.3.1 * Cherry-picked upstream commits (QGate stuff) in preparation for the 0.3.1 release * fix: use snapshot version after publish workflow * docs: add additional info for running business tests locally * feat(CI): add Markdown linter * md lint fix * pr remarks * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update .github/workflows/verify.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(md-linting): Fix markdown lint * fix: make AZKV clientsecret or certificate mutually exclusive * revert pointless blanks * fix: use correct paths for GH Packages docker reg. * fix: only dockerize if a dockerfile exists * chore: use old repo URL for Maven publication * fix: use PAT to publish to CXNG product-edc repo * PR Remarks * fix: remove duplicated code fragment in CHANGELOG * feat: removed backend service, replaced with JVM runner test moved consumer EDR controller to runtime module * docs: create decision record about renaming git branches * removed obsolete HTTP test * feat(charts): removes edc-controlplane and edc-dataplane charts * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * feat(dataEncryption): removes lombok from data-encryption module * Update edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Fix issue with sql pool * fix: add newline to file * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/setup-java from 3.10.0 to 3.11.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3.10.0 to 3.11.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3.10.0...v3.11.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * docs: create decision-record about refactoring helm charts * chore(deps): bump crazy-max/ghaction-import-gpg from 1 to 5 Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 1 to 5. - [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases) - [Changelog](https://github.com/crazy-max/ghaction-import-gpg/blob/v5/CHANGELOG.md) - [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/v1...v5) --- updated-dependencies: - dependency-name: crazy-max/ghaction-import-gpg dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump helm/chart-testing-action from 2.3.1 to 2.4.0 Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/helm/chart-testing-action/releases) - [Commits](https://github.com/helm/chart-testing-action/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: helm/chart-testing-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump mikefarah/yq from 4.31.2 to 4.33.3 Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.31.2 to 4.33.3. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.31.2...v4.33.3) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * feature: publish docker images to DockerHub * add manual docker-publish workflow * avoid input params, add concurrency * add checkout action * creds as action inputs * add jar build step * make namespace overridable * updated notices * incorporate new docker publish flow * update chart deployment specs * fix formatting * markdown lint * fix workflow * remove image namespace * prevent all interaction with dockerhub on pull requests * docs: add technical committer to pr_etiquette.md (#182) * chore: update to temurin 17 (#212) * chore: update dockerfiles and GH Actions to temurin 17 * pin specific version * feat(tests): removes lombok from edc-tests module (#159) * chore: add a template for pull request descriptions (#213) * fix: Adapt Helm Chart for version 0.3.x (#211) * Adapt Charts for version 0.3.x * fix business-tests * add edc.receiver.http.dynamic.endpoint * fix business-tests * code-review findings * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine from 7.11.1 to 7.11.2 (#221) * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine Bumps [io.cucumber:cucumber-junit-platform-engine](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-junit-platform-engine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump io.cucumber:cucumber-java from 7.11.1 to 7.11.2 (#225) Bumps [io.cucumber:cucumber-java](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:junit-jupiter from 1.17.6 to 1.18.0 (#224) Bumps [org.testcontainers:junit-jupiter](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1 (#222) Bumps com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1. --- updated-dependencies: - dependency-name: com.bmuschko.docker-remote-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:vault from 1.17.6 to 1.18.0 (#223) Bumps [org.testcontainers:vault](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:vault dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> * docs(control-plane-adapter): improve documentation on how to use the control-plane adapter extension (#210) * feature: create in-mem helm chart (#219) * feature: create the tractusx-connector-memory chart * pr remarks * pr remarks * increase waiting for negotiation, sometimes takes longer then 2 seconds * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * pr remarks * Update charts/tractusx-connector-memory/templates/deployment-runtime.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(deps): bump org.slf4j:slf4j-api from 2.0.3 to 2.0.7 (#234) Bumps [org.slf4j:slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.3 to 2.0.7. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.3...v_2.0.7) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.azure:azure-security-keyvault-secrets (#235) Bumps [com.azure:azure-security-keyvault-secrets](https://github.com/Azure/azure-sdk-for-java) from 4.5.4 to 4.6.0. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-security-keyvault-keys_4.5.4...azure-cosmos_4.6.0) --- updated-dependencies: - dependency-name: com.azure:azure-security-keyvault-secrets dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.diffplug.spotless from 6.15.0 to 6.18.0 (#236) Bumps com.diffplug.spotless from 6.15.0 to 6.18.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.github.johnrengelman.shadow from 8.0.0 to 8.1.1 (#237) * chore(deps): bump io.freefair.lombok from 6.6.2 to 8.0.1 (#238) * chore(deps): bump org.flywaydb:flyway-core from 9.15.2 to 9.16.3 (#242) * chore(deps): bump com.google.code.gson:gson from 2.10 to 2.10.1 (#243) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10 to 2.10.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10...gson-parent-2.10.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor: update GitHub output command to current version (#233) * refactor GitHub output command to current version * Remove curly braces from output statement * fix: only run trivy when docker images were actually built (#240) * fix: run trivy only if image exists * update checks * refactor: Extract the setup-java action into a re-usable action (#246) * Extract the checkout and setup-java action into a re-usable action * Commit actions. * fix action * remove checkout extraction * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation (#245) * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation * feat(BusinessPartnerValidation): adds logging on tests * feat(BusinessPartnerValidation): enabled by default on charts config * pr remarks * release-fix: use correct value * Release version 0.3.3 (#249) * Generate OpenApi Spec * feat(baseImage): replace alpine with temurin as base image for running java application * Lint and refactor mostly all *.md files * Lint new changes from develop branch * Replace appearance of product-edc with tractusx-edc * Fix README.md and Transfer Data.md * Fix Transfer Data.md * Regenerate helm chart README.md files * Remove left over html tags from root REAMDE.md * Add empty line at EOF * Update CODE_OF_CONDUCT.md * Retrigger ci * Release: fix version handling * Prepare release 0.3.1 * Cherry-picked upstream commits (QGate stuff) in preparation for the 0.3.1 release * fix: use snapshot version after publish workflow * docs: add additional info for running business tests locally * feat(CI): add Markdown linter * md lint fix * pr remarks * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update .github/workflows/verify.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(md-linting): Fix markdown lint * fix: make AZKV clientsecret or certificate mutually exclusive * revert pointless blanks * fix: use correct paths for GH Packages docker reg. * fix: only dockerize if a dockerfile exists * chore: use old repo URL for Maven publication * fix: use PAT to publish to CXNG product-edc repo * PR Remarks * fix: remove duplicated code fragment in CHANGELOG * feat: removed backend service, replaced with JVM runner test moved consumer EDR controller to runtime module * docs: create decision record about renaming git branches * removed obsolete HTTP test * feat(charts): removes edc-controlplane and edc-dataplane charts * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update docs/development/decision-records/2023-04-03_renaming_branches/README.md Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * feat(dataEncryption): removes lombok from data-encryption module * Update edc-extensions/data-encryption/src/test/java/org/eclipse/tractusx/edc/data/encryption/algorithms/aes/AesAlgorithmTest.java Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Fix issue with sql pool * fix: add newline to file * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump actions/setup-java from 3.10.0 to 3.11.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3.10.0 to 3.11.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v3.10.0...v3.11.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): bump alpine Bumps alpine from 3.17.2 to 3.17.3. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * docs: create decision-record about refactoring helm charts * chore(deps): bump crazy-max/ghaction-import-gpg from 1 to 5 Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 1 to 5. - [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases) - [Changelog](https://github.com/crazy-max/ghaction-import-gpg/blob/v5/CHANGELOG.md) - [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/v1...v5) --- updated-dependencies: - dependency-name: crazy-max/ghaction-import-gpg dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * chore(deps): bump helm/chart-testing-action from 2.3.1 to 2.4.0 Bumps [helm/chart-testing-action](https://github.com/helm/chart-testing-action) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/helm/chart-testing-action/releases) - [Commits](https://github.com/helm/chart-testing-action/compare/v2.3.1...v2.4.0) --- updated-dependencies: - dependency-name: helm/chart-testing-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore(deps): bump mikefarah/yq from 4.31.2 to 4.33.3 Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.31.2 to 4.33.3. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.31.2...v4.33.3) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * feature: publish docker images to DockerHub * add manual docker-publish workflow * avoid input params, add concurrency * add checkout action * creds as action inputs * add jar build step * make namespace overridable * updated notices * incorporate new docker publish flow * update chart deployment specs * fix formatting * markdown lint * fix workflow * remove image namespace * prevent all interaction with dockerhub on pull requests * docs: add technical committer to pr_etiquette.md (#182) * chore: update to temurin 17 (#212) * chore: update dockerfiles and GH Actions to temurin 17 * pin specific version * feat(tests): removes lombok from edc-tests module (#159) * chore: add a template for pull request descriptions (#213) * fix: Adapt Helm Chart for version 0.3.x (#211) * Adapt Charts for version 0.3.x * fix business-tests * add edc.receiver.http.dynamic.endpoint * fix business-tests * code-review findings * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine from 7.11.1 to 7.11.2 (#221) * refactor: rename git branches (#218) * refactor: update branch names and references in our documentation * publish packages to tractus-x * chore(deps): bump io.cucumber:cucumber-junit-platform-engine Bumps [io.cucumber:cucumber-junit-platform-engine](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-junit-platform-engine dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump io.cucumber:cucumber-java from 7.11.1 to 7.11.2 (#225) Bumps [io.cucumber:cucumber-java](https://github.com/cucumber/cucumber-jvm) from 7.11.1 to 7.11.2. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.1...v7.11.2) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-java dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:junit-jupiter from 1.17.6 to 1.18.0 (#224) Bumps [org.testcontainers:junit-jupiter](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1 (#222) Bumps com.bmuschko.docker-remote-api from 9.2.1 to 9.3.1. --- updated-dependencies: - dependency-name: com.bmuschko.docker-remote-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.testcontainers:vault from 1.17.6 to 1.18.0 (#223) Bumps [org.testcontainers:vault](https://github.com/testcontainers/testcontainers-java) from 1.17.6 to 1.18.0. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.17.6...1.18.0) --- updated-dependencies: - dependency-name: org.testcontainers:vault dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> * docs(control-plane-adapter): improve documentation on how to use the control-plane adapter extension (#210) * feature: create in-mem helm chart (#219) * feature: create the tractusx-connector-memory chart * pr remarks * pr remarks * increase waiting for negotiation, sometimes takes longer then 2 seconds * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * pr remarks * Update charts/tractusx-connector-memory/templates/deployment-runtime.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(deps): bump org.slf4j:slf4j-api from 2.0.3 to 2.0.7 (#234) Bumps [org.slf4j:slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.3 to 2.0.7. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.3...v_2.0.7) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.azure:azure-security-keyvault-secrets (#235) Bumps [com.azure:azure-security-keyvault-secrets](https://github.com/Azure/azure-sdk-for-java) from 4.5.4 to 4.6.0. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-security-keyvault-keys_4.5.4...azure-cosmos_4.6.0) --- updated-dependencies: - dependency-name: com.azure:azure-security-keyvault-secrets dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.diffplug.spotless from 6.15.0 to 6.18.0 (#236) Bumps com.diffplug.spotless from 6.15.0 to 6.18.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump com.github.johnrengelman.shadow from 8.0.0 to 8.1.1 (#237) * chore(deps): bump io.freefair.lombok from 6.6.2 to 8.0.1 (#238) * chore(deps): bump org.flywaydb:flyway-core from 9.15.2 to 9.16.3 (#242) * chore(deps): bump com.google.code.gson:gson from 2.10 to 2.10.1 (#243) Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10 to 2.10.1. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10...gson-parent-2.10.1) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor: update GitHub output command to current version (#233) * refactor GitHub output command to current version * Remove curly braces from output statement * fix: only run trivy when docker images were actually built (#240) * fix: run trivy only if image exists * update checks * refactor: Extract the setup-java action into a re-usable action (#246) * Extract the checkout and setup-java action into a re-usable action * Commit actions. * fix action * remove checkout extraction * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation (#245) * feat(BusinessPartnerValidation): adds logging if it's enabled on contract agreement validation * feat(BusinessPartnerValidation): adds logging on tests * feat(BusinessPartnerValidation): enabled by default on charts config * pr remarks * release-fix: use correct value * Prepare release 0.3.3 --------- Signed-off-by: dependabot[bot] Co-authored-by: Tuncay Tunc Co-authored-by: Enrico Risa Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Sebastian Bezold Co-authored-by: Paul Latzelsperger Co-authored-by: GitHub actions Co-authored-by: Stephan Bauer Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: Sigi <47592287+Siegfriedk@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> Co-authored-by: Sascha Isele (ZF Friedrichshafen AG) <127207440+saschaisele-zf@users.noreply.github.com> Co-authored-by: Garrett Smith <42892027+gcs14@users.noreply.github.com> * release-fix: allow manual entry of Docker tag * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * docs: add decision record about conventional commits (#255) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * docs: add decision record about conventional commits * fix: README.md points to wrong helm chart (#261) * Fix wrong helm install command * Update README.md * feature: add explicit docker image creation during release process (#251) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * feat(release): add explicit docker build job to release * simplify matrix * build(deps): add constraints to avoid vulnerable transitive dependencies (#259) * chore: Rename Veracode appname in CI job (#265) Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> * fix: Typo in veracode action (#267) * Adapt Postman collection for 0.3.x (#232) * feat: Add/update documentation for connector kit (#138) Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Stephan Bauer Co-authored-by: stephanbcbauer <84396022+stephanbcbauer@users.noreply.github.com> * feat: add GitHub workflow to automaticly add features to project (#264) * feature: refactor the main `tractusx-connector` chart (#230) * chore: Add 0.3.3 to, and fix markdown in CHANGELOG.md (#252) * feature: create new tractusx chart with Hashicorp and Postgres * lint * fix deployment test * updated urls * pr remarks * construct readiness URL directly in the test pod * Apply suggestions from code review Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Update charts/tractusx-connector/templates/tests/test-controlplane-readiness.yaml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * build(deps): Move Gradle dependencies constrains into root build.gradle.kts (#273) Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(deps): bump com.azure:azure-security-keyvault-secrets from 4.6.0 to 4.6.1 (#272) * chore(deps): bump com.azure:azure-security-keyvault-secrets Bumps [com.azure:azure-security-keyvault-secrets](https://github.com/Azure/azure-sdk-for-java) from 4.6.0 to 4.6.1. - [Release notes](https://github.com/Azure/azure-sdk-for-java/releases) - [Commits](https://github.com/Azure/azure-sdk-for-java/compare/azure-cosmos_4.6.0...azure-messaging-eventgrid_4.6.1) --- updated-dependencies: - dependency-name: com.azure:azure-security-keyvault-secrets dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * trigger-ci --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul Latzelsperger * chore(deps): bump actions/checkout from 3.3.0 to 3.5.2 (#254) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.3.0...v3.5.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): Move centralized dependency constrains to "allprojects" block within root build.gradle.kts (#274) * feat: delete add-to-project workflow (#276) This type of action doesn't work with the generated project. And there is a way to configure these kinds of workflows within the GitHub UI. * Update DEPENDENCIES file * Add license and copyright header to the charts * fix chart typo * fix charts * Update DEPENDENCIES file * Fix charts * Fix charts * Fix charts * Create new connector certificates * chore(test): use new certificate in the deployment test (#288) * chore(build): add GHA variables for sonar project and org (#287) * chore(build): add GHA variables for sonar project and org * trigger ci * chore(deps): bump org.junit:junit-bom from 5.9.2 to 5.9.3 (#290) Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.9.2...r5.9.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump org.junit.platform:junit-platform-suite (#291) Bumps [org.junit.platform:junit-platform-suite](https://github.com/junit-team/junit5) from 1.9.2 to 1.9.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/commits) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-suite dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(build): remove CI-triggered sonar job (#292) * chore: prepare Changelog and Migr. Guide for 0.3.4 (#298) * chore: prepare Changelog and Migr. Guide for 0.3.4 * Update docs/migration/Version_0.3.3_0.3.4.md Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> --------- Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> * chore(deps): bump org.flywaydb:flyway-core from 9.16.3 to 9.17.0 (#294) Bumps [org.flywaydb:flyway-core](https://github.com/flyway/flyway) from 9.16.3 to 9.17.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/commits/flyway-9.17.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor(chart): move test infrastructure into the test chart (#299) * chore(deps): bump io.cucumber:cucumber-junit-platform-engine (#300) Bumps [io.cucumber:cucumber-junit-platform-engine](https://github.com/cucumber/cucumber-jvm) from 7.11.2 to 7.12.0. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.2...v7.12.0) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-junit-platform-engine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump io.cucumber:cucumber-java from 7.11.2 to 7.12.0 (#301) Bumps [io.cucumber:cucumber-java](https://github.com/cucumber/cucumber-jvm) from 7.11.2 to 7.12.0. - [Release notes](https://github.com/cucumber/cucumber-jvm/releases) - [Changelog](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md) - [Commits](https://github.com/cucumber/cucumber-jvm/compare/v7.11.2...v7.12.0) --- updated-dependencies: - dependency-name: io.cucumber:cucumber-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: remove all printstacktrace statementsw (#304) * chore(build): use tractusx bot creds for release PRs etc. * feat(build): publish to OSSRH Snapshots and MavenCentral (#319) * docs: update code-of-conduct (#317) * chore(deps): bump alpine (#324) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump alpine (#325) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump alpine (#326) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump alpine (#323) Bumps alpine from 3.17.3 to 3.18.0. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(build): use composite action for GPG import (#320) * chore(build): use composite action for GPG import * Update .github/actions/import-gpg-key/action.yml Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore: Add .tractusx metafile (#335) * feature: create helm chart using the Azure KeyVault variant (#279) * feat: add Helm chart that utilized Azure KeyVault + Postgres * pr remarks * Update charts/tractusx-connector-azure-vault/README.md.gotmpl Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * use cUrl instead of wget do satisfy SonarCloud --------- Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * chore(helm): fix typo in required value in cp deployment (#337) In the controlplane deploment a required value was misspelled. Signed-off-by: Marco Lecheler * chore(deps): bump org.testcontainers:vault from 1.18.0 to 1.18.1 (#339) Bumps [org.testcontainers:vault](https://github.com/testcontainers/testcontainers-java) from 1.18.0 to 1.18.1. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.18.0...1.18.1) --- updated-dependencies: - dependency-name: org.testcontainers:vault dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(security): use uppercase AS in Dockerfiles (#341) * chore(deps): bump org.testcontainers:junit-jupiter from 1.18.0 to 1.18.1 (#340) Bumps [org.testcontainers:junit-jupiter](https://github.com/testcontainers/testcontainers-java) from 1.18.0 to 1.18.1. - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.18.0...1.18.1) --- updated-dependencies: - dependency-name: org.testcontainers:junit-jupiter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: replace using hard-coded certs with dynamically generated ones (#342) * feat: replace using hard-coded certs with dynamically generated ones * added az login * set AZ KeyVault secrets before deploy test * allow no sub * escape command * avoid logging of sensitive info * chore(deps): bump org.flywaydb:flyway-core from 9.17.0 to 9.18.0 (#349) Bumps [org.flywaydb:flyway-core](https://github.com/flyway/flyway) from 9.17.0 to 9.18.0. - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-9.17.0...flyway-9.18.0) --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs: add documentation about the helm charts (#352) * exclude charts/ from markdown lint, as the markdowns there are generated (#359) * chore(deps): bump helm/kind-action from 1.5.0 to 1.6.0 (#360) Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/helm/kind-action/releases) - [Commits](https://github.com/helm/kind-action/compare/v1.5.0...v1.6.0) --- updated-dependencies: - dependency-name: helm/kind-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: add migration for transferprocess_properties default value (#313) * Add migration for transferprocess_properties default value Signed-off-by: Brendan Cronin * Update edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_6__Snapshot_20230109_Update.sql Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * Move to new migration file Signed-off-by: Brendan Cronin * Copy+Paste=Bad Signed-off-by: Brendan Cronin * Empty commit * Update edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_7__Default_Value_For_Properties.sql --------- Signed-off-by: Brendan Cronin Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) * fix: Rename ingress endpoint from ids to protocol (#358) * Rename ingress endpoint from ids to protocol * Rename ingress endpoint from ids to protocol * fix service-runtime * fix renaming issues * Introduce new snapshot version 0.3.5-SNAPSHOT * chore: remove duplicated helm-release workflow (#372) * chore: remove duplicated helm-release workflow * fix omejdn chart * feature: switch to Dataspace Protocol (#378) * feat(dsp): preparatory work for 0.4.0, i.e. the new DSP * feat(Deps): updates to the EDC snapshot and implements a version catalog * pr remarks * pr remarks feat(ControlPlaneAdapter): callback integration for transfer process (#293) * feat(ControlPlaneAdapter): callback integration for transfer process * pr remarks * adds E2E test for opening a transfer * pr remarks * pr remarks * pr remarks + fix due snapshot upgrade * switched to EDC 0.0.1-20230509-preview-SNAPSHOT feat(ControlPlaneAdapter): edr cache integration on TransferProcessStarted event (#328) * feat(ControlPlaneAdapter): edr cache integration on TransferProcessStarted event * trigger CI * removed seed from file feat: Data Plane extensions that implement DSP/AAS integration (#357) * Add DPF extensions * Updates and improcvements based on Paul's review feat(EdrManagementApi): open transfer refactor (#347) * feat(EdrManagementApi): refactor open transfer + dsp protocol switch * feat(EdrManagementApi): updates EDC to 0.0.1-milestone-9 * use version catalogs * chore: annihilate business tests (#374) * chore(test): remove business tests * fix test-infra * add SQL migrations to reflect recent upstream changes * build: add license header check (#375) * build: add verification job to probe for license headers * remove unneeded classes * improved the command * make tests dependent on lic-header check * fix CI * chore: remove old control-plane adapter (#377) * add postgres dep * changelog and migration guide --------- Co-authored-by: Enrico Risa * removed version-catalog module * fix(migrations): remove transferprocess_properties column renaming (#380) * Prepare release 0.4.0 --------- Signed-off-by: dependabot[bot] Signed-off-by: Marco Lecheler Signed-off-by: Brendan Cronin Co-authored-by: Tuncay Tunc Co-authored-by: Enrico Risa Co-authored-by: Florian Rusch (ZF Friedrichshafen AG) Co-authored-by: Sebastian Bezold Co-authored-by: Paul Latzelsperger Co-authored-by: GitHub actions Co-authored-by: Stephan Bauer Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com> Co-authored-by: Sigi <47592287+Siegfriedk@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tuncay Tunc (ZF Friedrichshafen AG) <100704677+tuncaytunc-zf@users.noreply.github.com> Co-authored-by: Sascha Isele (ZF Friedrichshafen AG) <127207440+saschaisele-zf@users.noreply.github.com> Co-authored-by: Garrett Smith <42892027+gcs14@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: ndr_brt Co-authored-by: bcronin90 <90203222+bcronin90@users.noreply.github.com> Co-authored-by: stephanbcbauer <84396022+stephanbcbauer@users.noreply.github.com> Co-authored-by: Marco Lecheler Co-authored-by: eclipse-tractusx-bot --- .github/workflows/business-tests.yaml | 339 -------- .github/workflows/helm-chart-release.yaml | 57 -- .github/workflows/verify.yaml | 27 +- CHANGELOG.md | 338 ++++---- .../tractusx-connector-azure-vault/Chart.yaml | 4 +- .../tractusx-connector-azure-vault/README.md | 4 +- charts/tractusx-connector-memory/Chart.yaml | 4 +- charts/tractusx-connector-memory/README.md | 4 +- charts/tractusx-connector/Chart.yaml | 4 +- charts/tractusx-connector/README.md | 4 +- core/edr-cache-core/build.gradle.kts | 26 + .../edc/edr/core/EdrCacheCoreExtension.java | 46 ++ .../InMemoryEndpointDataReferenceCache.java | 99 +++ .../core/defaults/PersistentCacheEntry.java | 40 + ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + ...nMemoryEndpointDataReferenceCacheTest.java | 63 ++ .../defaults/PersistentCacheEntryTest.java | 53 ++ docs/development/Run-business-tests-local.md | 161 ---- docs/migration/Version_0.3.4_0.4.0.md | 29 + .../edc-controlplane-base/build.gradle.kts | 31 +- .../build.gradle.kts | 4 +- .../build.gradle.kts | 13 +- .../build.gradle.kts | 12 +- .../edc-runtime-memory/build.gradle.kts | 4 +- .../build.gradle.kts | 11 +- .../edc-dataplane-base/build.gradle.kts | 22 +- .../build.gradle.kts | 35 + .../DataPlaneProxyConsumerApiExtension.java | 109 +++ .../api/asset/ClientErrorExceptionMapper.java | 37 + .../api/asset/ConsumerAssetRequestApi.java | 38 + .../asset/ConsumerAssetRequestController.java | 155 ++++ .../asset/PreconditionFailedException.java | 29 + .../api/asset/model/AssetRequest.java | 85 ++ ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../api/asset/model/AssetRequestTest.java | 51 ++ .../build.gradle.kts | 36 + .../DataPlaneProxyProviderApiExtension.java | 88 +++ .../api/gateway/ProviderGatewayApi.java | 38 + .../gateway/ProviderGatewayController.java | 204 +++++ .../provider/api/response/ResponseHelper.java | 44 ++ ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../api/response/ResponseHelperTest.java | 32 + .../build.gradle.kts | 36 + .../core/ProxyProviderCoreExtension.java | 114 +++ .../AuthorizationHandlerRegistryImpl.java | 39 + .../gateway/auth/JwtAuthorizationHandler.java | 64 ++ .../core/gateway/auth/RsaPublicKeyParser.java | 56 ++ .../GatewayConfigurationLoader.java | 46 ++ .../GatewayConfigurationRegistryImpl.java | 39 + ...rg.eclipse.edc.spi.system.ServiceExtension | 13 + .../AuthorizationHandlerRegistryImplTest.java | 32 + .../auth/JwtAuthorizationHandlerTest.java | 81 ++ .../gateway/auth/RsaPublicKeyParserTest.java | 31 + .../core/gateway/auth/TestTokens.java | 43 + .../GatewayConfigurationLoaderTest.java | 52 ++ .../GatewayConfigurationRegistryImplTest.java | 31 + .../build.gradle.kts | 22 + .../authorization/AuthorizationExtension.java | 34 + .../authorization/AuthorizationHandler.java | 33 + .../AuthorizationHandlerRegistry.java | 35 + .../configuration/GatewayConfiguration.java | 81 ++ .../GatewayConfigurationRegistry.java | 35 + edc-extensions/build.gradle.kts | 1 - .../build.gradle.kts | 8 +- .../BusinessPartnerValidationExtension.java | 4 +- ...AbstractBusinessPartnerValidationTest.java | 4 +- .../build.gradle.kts | 30 + .../api/cp/adapter/AdapterApiExtension.java | 50 ++ .../edc/api/cp/adapter/AdapterEdrApi.java | 42 + .../api/cp/adapter/AdapterEdrController.java | 68 ++ .../adapter/dto/NegotiateEdrRequestDto.java | 123 +++ ...ctToNegotiateEdrRequestDtoTransformer.java | 89 +++ ...tDtoToNegotiatedEdrRequestTransformer.java | 64 ++ ...rg.eclipse.edc.spi.system.ServiceExtension | 15 + .../adapter/AdapterEdrApiExtensionTest.java | 65 ++ .../cp/adapter/AdapterEdrControllerTest.java | 113 +++ .../edc/api/cp/adapter/TestFunctions.java | 74 ++ .../NegotiateEdrRequestDtoValidationTest.java | 42 + ...NegotiateEdrRequestDtoTransformerTest.java | 154 ++++ ...oToNegotiateEdrRequestTransformerTest.java | 96 +++ .../build.gradle.kts | 30 + .../AdapterTransferProcessServiceImpl.java | 65 ++ .../callback/ContractNegotiationCallback.java | 81 ++ .../InProcessCallbackMessageDispatcher.java | 55 ++ .../InProcessCallbackRegistryExtension.java | 37 + .../InProcessCallbackRegistryImpl.java | 43 + .../callback/LocalCallbackExtension.java | 92 +++ .../TransferProcessLocalCallback.java | 77 ++ ...rg.eclipse.edc.spi.system.ServiceExtension | 16 + ...AdapterTransferProcessServiceImplTest.java | 90 +++ .../ContractNegotiationCallbackTest.java | 152 ++++ ...nProcessCallbackMessageDispatcherTest.java | 79 ++ ...nProcessCallbackRegistryExtensionTest.java | 41 + .../callback/LocalCallbackExtensionTest.java | 77 ++ .../cp/adapter/callback/TestFunctions.java | 96 +++ .../TransferProcessLocalCallbackTest.java | 191 +++++ .../control-plane-adapter/README.md | 98 --- .../control-plane-adapter/build.gradle.kts | 45 -- .../control-plane-adapter/docs/schema.sql | 54 -- .../edc/cp/adapter/ApiAdapterConfig.java | 100 --- .../edc/cp/adapter/ApiAdapterExtension.java | 247 ------ .../edc/cp/adapter/HttpController.java | 145 ---- .../dto/DataReferenceRetrievalDto.java | 25 - .../edc/cp/adapter/dto/ProcessData.java | 48 -- .../exception/ConfigurationException.java | 21 - .../exception/ExternalRequestException.java | 25 - .../exception/ResourceNotFoundException.java | 25 - .../edc/cp/adapter/messaging/Channel.java | 23 - .../adapter/messaging/InMemoryMessageBus.java | 69 -- .../edc/cp/adapter/messaging/Listener.java | 19 - .../cp/adapter/messaging/ListenerService.java | 44 -- .../edc/cp/adapter/messaging/Message.java | 65 -- .../edc/cp/adapter/messaging/MessageBus.java | 19 - .../cp/adapter/messaging/SqlMessageBus.java | 101 --- .../CatalogCachedRetriever.java | 50 -- .../contractnegotiation/CatalogRetriever.java | 68 -- .../ContractAgreementRetriever.java | 78 -- .../ContractNegotiationHandler.java | 144 ---- .../contractnotification/ContractInfo.java | 53 -- .../ContractNegotiationListenerImpl.java | 88 --- .../ContractNotificationHandler.java | 92 --- .../ContractNotificationSyncService.java | 32 - .../ContractSyncService.java | 108 --- .../DataTransferInitializer.java | 72 -- .../DataRefNotificationSyncService.java | 29 - .../datareference/DataRefSyncService.java | 70 -- .../DataReferenceErrorHandler.java | 58 -- .../datareference/DataReferenceHandler.java | 48 -- .../EndpointDataReferenceReceiverImpl.java | 53 -- .../adapter/service/ErrorResultService.java | 67 -- .../edc/cp/adapter/service/ResultService.java | 93 --- .../objectstore/ObjectStoreService.java | 27 - .../ObjectStoreServiceInMemory.java | 75 -- .../objectstore/ObjectStoreServiceSql.java | 86 -- .../service/objectstore/ObjectType.java | 22 - .../edc/cp/adapter/store/SqlObjectStore.java | 118 --- .../edc/cp/adapter/store/SqlQueueStore.java | 184 ----- .../cp/adapter/store/model/ObjectEntity.java | 29 - .../cp/adapter/store/model/QueueMessage.java | 31 - .../BaseSqlDialectObjectStoreStatements.java | 56 -- .../schema/BaseSqlDialectQueueStatements.java | 103 --- .../store/schema/ObjectStoreStatements.java | 45 -- .../adapter/store/schema/QueueStatements.java | 56 -- .../PostgresDialectObjectStoreStatements.java | 28 - .../PostgresDialectQueueStatements.java | 29 - .../edc/cp/adapter/util/ExpiringMap.java | 70 -- .../tractusx/edc/cp/adapter/util/LockMap.java | 50 -- ...rg.eclipse.edc.spi.system.ServiceExtension | 14 - .../main/resources/control-plane-adapter.jpg | Bin 205271 -> 0 bytes .../edc/cp/adapter/HttpControllerTest.java | 121 --- .../messaging/InMemoryMessageBusTest.java | 68 -- .../adapter/messaging/SqlMessageBusTest.java | 235 ------ .../CatalogRetrieverTest.java | 92 --- .../ContractAgreementRetrieverTest.java | 86 -- .../ContractNegotiationHandlerTest.java | 175 ----- .../ContractNegotiationListenerTest.java | 148 ---- .../ContractNotificationHandlerTest.java | 141 ---- .../ContractSyncServiceTest.java | 143 ---- .../datareference/DataRefSyncServiceTest.java | 98 --- .../DataReferenceErrorHandlerTest.java | 110 --- .../DataReferenceHandlerTest.java | 85 -- .../EndpointDataReferenceReceiverTest.java | 84 -- .../cp/adapter/service/ResultServiceTest.java | 129 --- .../edc/cp/adapter/util/ExpiringMapTest.java | 65 -- edc-extensions/cx-oauth2/build.gradle.kts | 6 +- .../data-encryption/build.gradle.kts | 6 +- .../build.gradle.kts | 10 +- .../hashicorp-vault/build.gradle.kts | 6 +- .../build.gradle.kts | 12 +- .../postgresql-migration/build.gradle.kts | 11 +- ...Alter_Asset_Property_add_IsPrivateFlag.sql | 16 + ...ter_ContractDefinition_Remove_validity.sql | 16 + ...lter_ContractNegotiation_Add_Callbacks.sql | 15 + ..._Alter_ContractNegotiation_Change_Type.sql | 16 + ...8__Alter_TransferProcess_Add_Callbacks.sql | 15 + ...er_TransferProcess_Remove_Transfertype.sql | 16 + .../build.gradle.kts | 11 +- ...ovisionAdditionalHeadersExtensionTest.java | 118 +-- .../build.gradle.kts | 18 +- .../sftp/client/SftpDataSink.java | 31 +- .../sftp/client/SftpDataSource.java | 17 +- .../sftp/client/SftpDataSourceTest.java | 55 +- .../build.gradle.kts | 4 +- .../build.gradle.kts | 8 +- edc-tests/cucumber/README.md | 19 - edc-tests/cucumber/build.gradle.kts | 60 -- .../deployment/helm/omejdn/.helmignore | 23 - .../deployment/helm/omejdn/Chart.yaml | 39 - .../deployment/helm/omejdn/README.md | 21 - .../helm/omejdn/templates/_helpers.tpl | 64 -- .../helm/omejdn/templates/configmap.yaml | 93 --- .../helm/omejdn/templates/deployment.yaml | 164 ---- .../deployment/helm/omejdn/templates/hpa.yaml | 47 -- .../omejdn/templates/imagepullsecret.yaml | 31 - .../helm/omejdn/templates/service.yaml | 34 - .../helm/omejdn/templates/serviceaccount.yaml | 31 - .../deployment/helm/omejdn/values.yaml | 109 --- .../helm/supporting-infrastructure/.gitignore | 4 - .../supporting-infrastructure/.helmignore | 24 - .../helm/supporting-infrastructure/Chart.yaml | 74 -- .../helm/supporting-infrastructure/README.md | 95 --- .../diagrams/deployed_components.png | Bin 22169 -> 0 bytes .../diagrams/deployed_components.puml | 20 - .../supporting-infrastructure/values.yaml | 309 -------- .../tractusx/edc/tests/AssetStepDefs.java | 63 -- .../edc/tests/BackendDataService.java | 35 - .../edc/tests/BackendServiceSteps.java | 33 - .../tractusx/edc/tests/CatalogStepDefs.java | 92 --- .../eclipse/tractusx/edc/tests/Connector.java | 90 --- .../tractusx/edc/tests/ConnectorFactory.java | 44 -- .../tractusx/edc/tests/ConnectorSteps.java | 32 - .../eclipse/tractusx/edc/tests/Constants.java | 35 - .../edc/tests/ContractDefinitionStepDefs.java | 60 -- .../edc/tests/ControlPlaneAdapterSteps.java | 87 --- .../tractusx/edc/tests/DataManagementAPI.java | 739 ------------------ .../tractusx/edc/tests/Environment.java | 199 ----- .../edc/tests/HttpProxyTransferSteps.java | 124 --- .../tractusx/edc/tests/NegotiationSteps.java | 95 --- .../tractusx/edc/tests/PolicyStepDefs.java | 73 -- .../edc/tests/S3FileTransferStepsDefs.java | 178 ----- .../tractusx/edc/tests/data/Asset.java | 46 -- .../data/BusinessPartnerNumberConstraint.java | 35 - .../tractusx/edc/tests/data/Constraint.java | 23 - .../edc/tests/data/ContractDefinition.java | 61 -- .../edc/tests/data/ContractNegotiation.java | 48 -- .../tests/data/ContractNegotiationState.java | 29 - .../edc/tests/data/ContractOffer.java | 46 -- .../tractusx/edc/tests/data/DataAddress.java | 22 - .../tests/data/HttpProxySinkDataAddress.java | 23 - .../data/HttpProxySourceDataAddress.java | 71 -- .../tractusx/edc/tests/data/Negotiation.java | 62 -- .../edc/tests/data/NullDataAddress.java | 30 - .../tractusx/edc/tests/data/OrConstraint.java | 37 - .../edc/tests/data/PayMeConstraint.java | 38 - .../tractusx/edc/tests/data/Permission.java | 49 -- .../tractusx/edc/tests/data/Policy.java | 43 - .../edc/tests/data/S3DataAddress.java | 42 - .../tractusx/edc/tests/data/Transfer.java | 60 -- .../edc/tests/data/TransferProcess.java | 40 - .../edc/tests/data/TransferProcessState.java | 26 - .../edc/tests/features/ParameterTypes.java | 33 - .../edc/tests/features/RunCucumberTest.java | 28 - .../edc/tests/util/DatabaseCleaner.java | 56 -- .../tractusx/edc/tests/util/S3Client.java | 126 --- .../tractusx/edc/tests/util/Timeouts.java | 30 - .../test/resources/junit-platform.properties | 3 - .../src/test/resources/logback-test.xml | 36 - .../features/ContractNegotiation.feature | 58 -- .../edc/tests/features/ContractOffers.feature | 97 --- .../tests/features/EndToEndTransfer.feature | 43 - .../features/HttpProxyDataTransfer.feature | 43 - .../edc/tests/features/S3FileTransfer.feature | 48 -- .../resources/helm/omejdn/templates/hpa.yaml | 32 +- edc-tests/e2e-tests/build.gradle.kts | 26 +- .../edc/helpers/AssetHelperFunctions.java | 57 ++ .../edc/helpers/CatalogHelperFunctions.java | 71 ++ .../ContractDefinitionHelperFunctions.java | 42 + .../ContractNegotiationHelperFunctions.java | 47 ++ .../EdrNegotiationHelperFunctions.java | 64 ++ .../edc/helpers/PolicyHelperFunctions.java | 109 +++ .../edc/helpers/QueryHelperFunctions.java | 34 + .../TransferProcessHelperFunctions.java | 50 ++ .../tractusx/edc/lifecycle/DataWiper.java | 2 +- .../edc/lifecycle/MultiRuntimeTest.java | 40 +- .../tractusx/edc/lifecycle/Participant.java | 299 +++---- .../lifecycle/TestRuntimeConfiguration.java | 14 +- .../edc/policy/PolicyHelperFunctions.java | 69 -- .../tractusx/edc/tests/CatalogTest.java | 89 ++- .../tests/HttpConsumerPullWithProxyTest.java | 50 +- .../tractusx/edc/tests/NegotiateEdrTest.java | 164 ++++ .../edc-dataplane-proxy-e2e/build.gradle.kts | 37 + .../proxy/e2e/DpfProxyEndToEndTest.java | 193 +++++ .../dataplane/proxy/e2e/EdrCacheSetup.java | 101 +++ .../dataplane/proxy/e2e/KeyStoreSetup.java | 45 ++ .../edc/dataplane/proxy/e2e/VaultSetup.java | 46 ++ edc-tests/runtime/build.gradle.kts | 3 +- gradle.properties | 9 +- gradle/libs.versions.toml | 132 ++++ .../yaml/control-plane-adapter-api.yaml | 324 ++++++++ .../yaml/observability-api-customization.yaml | 105 +++ settings.gradle.kts | 143 +--- .../build.gradle.kts | 26 + .../adapter/callback/InProcessCallback.java | 28 + .../callback/InProcessCallbackRegistry.java | 43 + .../cp/adapter/model/NegotiateEdrRequest.java | 102 +++ .../AdapterTransferProcessService.java | 34 + spi/edr-cache-spi/build.gradle.kts | 22 + .../edr/spi/EndpointDataReferenceCache.java | 57 ++ .../edr/spi/EndpointDataReferenceEntry.java | 94 +++ .../spi/EndpointDataReferenceEntryTest.java | 43 + 290 files changed, 7464 insertions(+), 11242 deletions(-) delete mode 100644 .github/workflows/business-tests.yaml delete mode 100644 .github/workflows/helm-chart-release.yaml create mode 100644 core/edr-cache-core/build.gradle.kts create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java create mode 100644 core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java create mode 100644 core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java create mode 100644 core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java delete mode 100644 docs/development/Run-business-tests-local.md create mode 100644 docs/migration/Version_0.3.4_0.4.0.md create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java create mode 100644 edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java create mode 100644 edc-extensions/control-plane-adapter-api/build.gradle.kts create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java create mode 100644 edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java create mode 100644 edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/build.gradle.kts create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java create mode 100644 edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java delete mode 100644 edc-extensions/control-plane-adapter/README.md delete mode 100644 edc-extensions/control-plane-adapter/build.gradle.kts delete mode 100644 edc-extensions/control-plane-adapter/docs/schema.sql delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterConfig.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterExtension.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/HttpController.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/DataReferenceRetrievalDto.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/ProcessData.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ConfigurationException.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ExternalRequestException.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ResourceNotFoundException.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Channel.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBus.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Listener.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/ListenerService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Message.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/MessageBus.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBus.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogCachedRetriever.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetriever.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractInfo.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationSyncService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefNotificationSyncService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandler.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverImpl.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ErrorResultService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreService.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectType.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/ObjectEntity.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/QueueMessage.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectObjectStoreStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectQueueStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/ObjectStoreStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/QueueStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectObjectStoreStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectQueueStatements.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMap.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/LockMap.java delete mode 100644 edc-extensions/control-plane-adapter/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension delete mode 100644 edc-extensions/control-plane-adapter/src/main/resources/control-plane-adapter.jpg delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/HttpControllerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBusTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBusTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncServiceTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncServiceTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandlerTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java delete mode 100644 edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMapTest.java create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/asset/V0_0_4__Alter_Asset_Property_add_IsPrivateFlag.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractdefinition/V0_0_5__Alter_ContractDefinition_Remove_validity.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_6__Alter_ContractNegotiation_Change_Type.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_8__Alter_TransferProcess_Add_Callbacks.sql create mode 100644 edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_9__Alter_TransferProcess_Remove_Transfertype.sql delete mode 100644 edc-tests/cucumber/README.md delete mode 100644 edc-tests/cucumber/build.gradle.kts delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/.helmignore delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/README.md delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.gitignore delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.helmignore delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/README.md delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.png delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.puml delete mode 100644 edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/AssetStepDefs.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/CatalogStepDefs.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorSteps.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ContractDefinitionStepDefs.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiationState.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/DataAddress.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcessState.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/ParameterTypes.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/RunCucumberTest.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java delete mode 100644 edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/Timeouts.java delete mode 100644 edc-tests/cucumber/src/test/resources/junit-platform.properties delete mode 100644 edc-tests/cucumber/src/test/resources/logback-test.xml delete mode 100644 edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractNegotiation.feature delete mode 100644 edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractOffers.feature delete mode 100644 edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/EndToEndTransfer.feature delete mode 100644 edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature delete mode 100644 edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/S3FileTransfer.feature create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java delete mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java create mode 100644 edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/EdrCacheSetup.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/KeyStoreSetup.java create mode 100644 edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java create mode 100644 gradle/libs.versions.toml create mode 100644 resources/openapi/yaml/control-plane-adapter-api.yaml create mode 100644 resources/openapi/yaml/observability-api-customization.yaml create mode 100644 spi/control-plane-adapter-spi/build.gradle.kts create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallback.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallbackRegistry.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/model/NegotiateEdrRequest.java create mode 100644 spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/service/AdapterTransferProcessService.java create mode 100644 spi/edr-cache-spi/build.gradle.kts create mode 100644 spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceCache.java create mode 100644 spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntry.java create mode 100644 spi/edr-cache-spi/src/test/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntryTest.java diff --git a/.github/workflows/business-tests.yaml b/.github/workflows/business-tests.yaml deleted file mode 100644 index 1bc3436bc..000000000 --- a/.github/workflows/business-tests.yaml +++ /dev/null @@ -1,339 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - ---- -name: "Business Tests" - -on: - pull_request: - paths-ignore: - - 'docs/**' - - '**/*.md' - branches: - - releases - - release/** - - main - workflow_dispatch: - -concurrency: - # cancel only running jobs on pull requests - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - business-test: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - ############## - ### Set-Up ### - ############## - - - uses: actions/checkout@v3.5.2 - - - uses: ./.github/actions/setup-java - - - name: Cache ContainerD Image Layers - uses: actions/cache@v3 - with: - path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs - key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs - - - name: Set-Up Kubectl - uses: azure/setup-kubectl@v3.2 - - - name: Helm Set-Up - uses: azure/setup-helm@v3.5 - with: - version: v3.8.1 - - - name: Create k8s Kind Cluster configuration (kind.config.yaml) - run: |- - export MAVEN_REPOSITORY=${{ github.workspace }}/.m2/repository - cat << EOF > kind.config.yaml - --- - kind: Cluster - apiVersion: kind.x-k8s.io/v1alpha4 - nodes: - - role: control-plane - extraMounts: - - hostPath: ${PWD} - containerPath: /srv/tractusx-edc - - hostPath: ${MAVEN_REPOSITORY} - containerPath: /srv/m2-repository - - hostPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs - containerPath: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs - EOF - - - name: Create k8s Kind Cluster - uses: helm/kind-action@v1.6.0 - with: - config: kind.config.yaml - - ############################################## - ### Build and load recent images into KinD ### - ############################################## - - - name: Build edc-controlplane-postgresql-hashicorp-vault - run: |- - ./gradlew :edc-controlplane:edc-controlplane-postgresql-hashicorp-vault:dockerize - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: Build edc-dataplane-hashicorp-vault - run: |- - ./gradlew :edc-dataplane:edc-dataplane-hashicorp-vault:dockerize - env: - GITHUB_PACKAGE_USERNAME: ${{ github.actor }} - GITHUB_PACKAGE_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - - - name: Load images into KinD - run: |- - docker tag edc-controlplane-postgresql-hashicorp-vault:latest edc-controlplane-postgresql-hashicorp-vault:business-test - docker tag edc-dataplane-hashicorp-vault:latest edc-dataplane-hashicorp-vault:business-test - kind get clusters | xargs -n1 kind load docker-image edc-controlplane-postgresql-hashicorp-vault:business-test edc-dataplane-hashicorp-vault:business-test --name - - ############################################ - ### Prepare And Install Test Environment ### - ############################################ - - - name: Define test environment variables - run: |- - # Define endpoints - echo "SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY=password" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATA_MANAGEMENT_URL=http://sokrates-controlplane:8081/management" | tee -a ${GITHUB_ENV} - echo "SOKRATES_IDS_URL=http://sokrates-controlplane:8084/api/v1/ids" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATA_PLANE_URL=http://sokrates-dataplane:8081/api/public/" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATABASE_URL=jdbc:postgresql://plato-postgresql:5432/edc" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATABASE_USER=user" | tee -a ${GITHUB_ENV} - echo "SOKRATES_DATABASE_PASSWORD=password" | tee -a ${GITHUB_ENV} - echo "PLATO_DATA_MANAGEMENT_API_AUTH_KEY=password" | tee -a ${GITHUB_ENV} - echo "PLATO_DATA_MANAGEMENT_URL=http://plato-controlplane:8081/management" | tee -a ${GITHUB_ENV} - echo "PLATO_IDS_URL=http://plato-controlplane:8084/api/v1/ids" | tee -a ${GITHUB_ENV} - echo "PLATO_DATA_PLANE_URL=http://plato-dataplane:8081/api/public/" | tee -a ${GITHUB_ENV} - echo "PLATO_DATABASE_URL=jdbc:postgresql://plato-postgresql:5432/edc" | tee -a ${GITHUB_ENV} - echo "PLATO_DATABASE_USER=user" | tee -a ${GITHUB_ENV} - echo "PLATO_DATABASE_PASSWORD=password" | tee -a ${GITHUB_ENV} - echo "EDC_AWS_ENDPOINT_OVERRIDE=http://minio:9000" | tee -a ${GITHUB_ENV} - echo "PLATO_AWS_SECRET_ACCESS_KEY=platoqwerty123" | tee -a ${GITHUB_ENV} - echo "PLATO_AWS_ACCESS_KEY_ID=platoqwerty123" | tee -a ${GITHUB_ENV} - echo "SOKRATES_AWS_SECRET_ACCESS_KEY=sokratesqwerty123" | tee -a ${GITHUB_ENV} - echo "SOKRATES_AWS_ACCESS_KEY_ID=sokratesqwerty123" | tee -a ${GITHUB_ENV} - - - name: Install infrastructure components via Helm - uses: nick-fields/retry@v2 - with: - timeout_minutes: 10 - max_attempts: 3 - retry_on: error - command: |- - # Update helm dependencies - helm dependency update edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure - - # Install the all-in-one supporting infrastructure environment (daps, vault, pgsql, minio) - helm install infrastructure edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure \ - --wait-for-jobs --timeout=120s - - # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s - sleep 5s - - # Wait for supporting infrastructure to become ready (control-/data-plane, backend service) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=idsdaps --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=idsdaps --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=vault --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=vault --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sokrates-postgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=sokrates-postgresql --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=plato-postgresql --timeout=120s || ( kubectl logs -l app.kubernetes.io/name=plato-postgresql --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app=minio --timeout=120s || ( kubectl logs -l app=minio --tail 500 && exit 1 ) - - # Install Plato - helm install plato charts/tractusx-connector \ - --set fullnameOverride=plato \ - --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.management.authKey=password \ - --set controlplane.image.tag=business-test \ - --set controlplane.image.pullPolicy=Never \ - --set controlplane.image.repository=docker.io/library/edc-controlplane-postgresql-hashicorp-vault \ - --set dataplane.image.tag=business-test \ - --set dataplane.image.pullPolicy=Never \ - --set dataplane.image.repository=docker.io/library/edc-dataplane-hashicorp-vault \ - --set controlplane.debug.enabled=true \ - --set controlplane.suspendOnStart=false \ - --set controlplane.businesspartnervalidation.log.agreement.validation=true \ - --set postgresql.enabled=true \ - --set postgresql.username=user \ - --set postgresql.password=password \ - --set postgresql.jdbcUrl=jdbc:postgresql://plato-postgresql:5432/edc \ - --set vault.hashicorp.enabled=true \ - --set vault.hashicorp.url=http://vault:8200 \ - --set vault.hashicorp.token=root \ - --set vault.secretNames.transferProxyTokenSignerPublicKey=plato/daps/my-plato-daps-crt \ - --set vault.secretNames.transferProxyTokenSignerPrivateKey=plato/daps/my-plato-daps-key \ - --set vault.secretNames.transferProxyTokenEncryptionAesKey=plato/data-encryption-aes-keys \ - --set vault.secretNames.dapsPrivateKey=plato/daps/my-plato-daps-key \ - --set vault.secretNames.dapsPublicKey=plato/daps/my-plato-daps-crt \ - --set daps.url=http://ids-daps:4567 \ - --set daps.clientId=99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23 \ - --set dataplane.aws.endpointOverride=http://minio:9000 \ - --set dataplane.aws.secretAccessKey=platoqwerty123 \ - --set dataplane.aws.accessKeyId=platoqwerty123 \ - --set backendService.httpProxyTokenReceiverUrl=http://backend:8080 \ - --wait-for-jobs --timeout=120s - - # Install Sokrates - helm install sokrates charts/tractusx-connector \ - --set fullnameOverride=sokrates \ - --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.management.authKey=password \ - --set controlplane.image.tag=business-test \ - --set controlplane.image.pullPolicy=Never \ - --set controlplane.image.repository=docker.io/library/edc-controlplane-postgresql-hashicorp-vault \ - --set dataplane.image.tag=business-test \ - --set dataplane.image.pullPolicy=Never \ - --set dataplane.image.repository=docker.io/library/edc-dataplane-hashicorp-vault \ - --set controlplane.debug.enabled=true \ - --set controlplane.suspendOnStart=false \ - --set controlplane.businesspartnervalidation.log.agreement.validation=true \ - --set postgresql.enabled=true \ - --set postgresql.username=user \ - --set postgresql.password=password \ - --set postgresql.jdbcUrl=jdbc:postgresql://sokrates-postgresql:5432/edc \ - --set vault.hashicorp.enabled=true \ - --set vault.hashicorp.url=http://vault:8200 \ - --set vault.hashicorp.token=root \ - --set vault.secretNames.transferProxyTokenSignerPublicKey=sokrates/daps/my-sokrates-daps-crt \ - --set vault.secretNames.transferProxyTokenSignerPrivateKey=sokrates/daps/my-sokrates-daps-key \ - --set vault.secretNames.transferProxyTokenEncryptionAesKey=sokrates/data-encryption-aes-keys \ - --set vault.secretNames.dapsPrivateKey=sokrates/daps/my-sokrates-daps-key \ - --set vault.secretNames.dapsPublicKey=sokrates/daps/my-sokrates-daps-crt \ - --set daps.url=http://ids-daps:4567 \ - --set daps.clientId=E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65 \ - --set dataplane.aws.endpointOverride=http://minio:9000 \ - --set dataplane.aws.secretAccessKey=sokratesqwerty123 \ - --set dataplane.aws.accessKeyId=sokratesqwerty123 \ - --set backendService.httpProxyTokenReceiverUrl=http://backend:8080 \ - --wait-for-jobs --timeout=120s - - # GH pipelines constrained by cpu, so give helm some time to register all resources \w k8s - sleep 5s - - # Wait for Control-/DataPlane and backend-service to become ready - kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=sokrates-controlplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=sokrates-controlplane --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=sokrates-dataplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=sokrates-dataplane --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=plato-controlplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=plato-controlplane --tail 500 && exit 1 ) - kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=plato-dataplane --timeout=600s || ( kubectl logs -l app.kubernetes.io/instance=plato-dataplane --tail 500 && exit 1 ) - - ############################################## - ### Run Business Tests inside kind cluster ### - ############################################## - - - name: Run Business Tests - run: |- - cat << EOF >> pod.json - { - "apiVersion": "v1", - "kind": "Pod", - "spec": { - "containers": [ - { - "args": [ - "-c", - "cd /tractusx-edc && ./gradlew edc-tests:cucumber:test -Dcucumber=true" - ], - "command": [ - "/bin/sh" - ], - EOF - - # Ugly hack to get env vars passed into the k8s-run - if '--overrides' defined '--env' is ignored :( - cat << EOF >> pod.json - "env": [ - {"name": "SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY", "value": "${SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY}"}, - {"name": "PLATO_DATA_MANAGEMENT_API_AUTH_KEY", "value": "${PLATO_DATA_MANAGEMENT_API_AUTH_KEY}"}, - {"name": "SOKRATES_DATA_MANAGEMENT_URL", "value": "${SOKRATES_DATA_MANAGEMENT_URL}"}, - {"name": "SOKRATES_IDS_URL", "value": "${SOKRATES_IDS_URL}"}, - {"name": "SOKRATES_DATA_PLANE_URL", "value": "${SOKRATES_DATA_PLANE_URL}"}, - {"name": "SOKRATES_BACKEND_SERVICE_BACKEND_API_URL", "value": "http://backend:8081" }, - {"name": "SOKRATES_DATABASE_URL", "value": "${SOKRATES_DATABASE_URL}"}, - {"name": "SOKRATES_DATABASE_USER", "value": "${SOKRATES_DATABASE_USER}"}, - {"name": "SOKRATES_DATABASE_PASSWORD", "value": "${SOKRATES_DATABASE_PASSWORD}"}, - {"name": "PLATO_DATA_MANAGEMENT_URL", "value": "${PLATO_DATA_MANAGEMENT_URL}"}, - {"name": "PLATO_IDS_URL", "value": "${PLATO_IDS_URL}"}, - {"name": "PLATO_DATA_PLANE_URL", "value": "${PLATO_DATA_PLANE_URL}"}, - {"name": "PLATO_BACKEND_SERVICE_BACKEND_API_URL", "value": "http://backend:8081"}, - {"name": "PLATO_DATABASE_URL", "value": "${PLATO_DATABASE_URL}"}, - {"name": "PLATO_DATABASE_USER", "value": "${PLATO_DATABASE_USER}"}, - {"name": "PLATO_DATABASE_PASSWORD", "value": "${PLATO_DATABASE_PASSWORD}"}, - {"name": "EDC_AWS_ENDPOINT_OVERRIDE", "value": "${EDC_AWS_ENDPOINT_OVERRIDE}"}, - {"name": "PLATO_AWS_SECRET_ACCESS_KEY", "value": "${PLATO_AWS_SECRET_ACCESS_KEY}"}, - {"name": "PLATO_AWS_ACCESS_KEY_ID", "value": "${PLATO_AWS_ACCESS_KEY_ID}"}, - {"name": "SOKRATES_AWS_SECRET_ACCESS_KEY", "value": "${SOKRATES_AWS_SECRET_ACCESS_KEY}"}, - {"name": "SOKRATES_AWS_ACCESS_KEY_ID", "value": "${SOKRATES_AWS_ACCESS_KEY_ID}"} - ], - EOF - - cat << EOF >> pod.json - "image": "openjdk:11-jdk-slim", - "name": "edc-tests-cucumber", - "volumeMounts": [ - { - "mountPath": "/tractusx-edc", - "name": "tractusx-edc" - }, - { - "mountPath": "/root/.m2/repository", - "name": "m2-repository" - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Never", - "volumes": [ - { - "hostPath": { - "path": "/srv/tractusx-edc" - }, - "name": "tractusx-edc" - }, - { - "hostPath": { - "path": "/srv/m2-repository" - }, - "name": "m2-repository" - } - ] - } - } - EOF - - kubectl run -i --image=openjdk:11-jdk-slim --restart=Never --rm edc-tests-cucumber --overrides="$(cat pod.json)" - - ################# - ### Tear Down ### - ################# - - - name: Destroy the kind cluster - if: always() - run: >- - kind get clusters | xargs -n1 kind delete cluster --name diff --git a/.github/workflows/helm-chart-release.yaml b/.github/workflows/helm-chart-release.yaml deleted file mode 100644 index 0e282826f..000000000 --- a/.github/workflows/helm-chart-release.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0. -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -name: Release - Helm Charts - -on: - push: - paths: - - 'charts/**' - branches: - - releases - workflow_dispatch: - -jobs: - release: - # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions - # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token - permissions: - contents: write - packages: write - pages: write - runs-on: ubuntu-latest - - steps: - # fetch-depth: 0 is required to determine differences in chart(s) - - uses: actions/checkout@v3.5.2 - with: - fetch-depth: 0 - - - name: Configure Git - run: | - git config user.name "eclipse-tractusx-bot" - git config user.email "tractusx-bot@eclipse.org" - - name: Install Helm - uses: azure/setup-helm@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Run chart-releaser - uses: helm/chart-releaser-action@v1.5.0 - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index c826f39b6..310c78527 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -34,8 +34,6 @@ on: pull_request: paths-ignore: - 'charts/**' - branches: - - '*' workflow_dispatch: concurrency: @@ -44,7 +42,22 @@ concurrency: cancel-in-progress: true jobs: - + + verify-license-headers: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3.5.2 + - name: "Check for files without a license header" + run: |- + # checks all java, yaml, kts and sql files for an Apache 2.0 license header + cmd="grep -riL \"SPDX-License-Identifier: Apache-2.0\" --include=\*.{java,yaml,yml,kts,sql} --exclude-dir={.gradle,\*\openapi} ." + violations=$(eval $cmd | wc -l) + if [[ $violations -ne 0 ]] ; then + echo "$violations files without license headers were found:"; + eval $cmd; + exit 1; + fi + verify-formatting: runs-on: ubuntu-latest steps: @@ -73,7 +86,7 @@ jobs: unit-tests: runs-on: ubuntu-latest - needs: [ verify-formatting ] + needs: [ verify-formatting, verify-license-headers ] steps: - uses: actions/checkout@v3.5.2 @@ -84,7 +97,7 @@ jobs: integration-tests: runs-on: ubuntu-latest - needs: [ verify-formatting ] + needs: [ verify-formatting, verify-license-headers ] steps: - uses: actions/checkout@v3.5.2 @@ -95,7 +108,7 @@ jobs: api-tests: runs-on: ubuntu-latest - needs: [ verify-formatting ] + needs: [ verify-formatting, verify-license-headers ] steps: - uses: actions/checkout@v3.5.2 @@ -106,7 +119,7 @@ jobs: end-to-end-tests: runs-on: ubuntu-latest - needs: [ verify-formatting ] + needs: [ verify-formatting, verify-license-headers ] steps: - uses: actions/checkout@v3.5.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 842715f6c..f3360a429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,29 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0] - 2023-05-18 + +## [0.4.0] - 2023-05-18 + +## Added + +- Support for the new Dataspace Protocol +- GitHub Workflow to check for missing license headers + +## Changed + +- Switched to Eclipse Dataspace Components `0.0.1-milestone-9` + +## Removed + +- Business tests. All tests are covered by other means. +- Control-Plane-Adapter. Replaced by a DSP-compatible implementation + ## [0.3.4] - 2023-05-17 ### Fixed -- Added license headers to several files in the code base -- Refactoring of Helm charts - multiple charts instead of one dynamically assembled chart +- Added license headers to several files in the code base +- Refactoring of Helm charts - multiple charts instead of one dynamically assembled chart ## [0.3.3] - 2023-04-19 ### Fixed -- Config values for the data plane part of the helm chart -- Contract Validity +- Config values for the data plane part of the helm chart +- Contract Validity ### Added -- A log line whenever a policy evaluation of the BPN number was performed +- A log line whenever a policy evaluation of the BPN number was performed ## [0.3.2] - 2023-03-30 ### Fixed -- Fixed mutually-exclusive config values for Azure KeyVault +- Fixed mutually-exclusive config values for Azure KeyVault ## [0.3.1] - 2023-03-27 @@ -37,7 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Support unauthenticated access to the ObservabilityAPI (#126) +- Support unauthenticated access to the ObservabilityAPI (#126) ### Fixed @@ -48,144 +66,144 @@ corresponding [documentation](/docs/migration/Version_0.1.x_0.3.x.md). ### Added -- Add contract id to data source http call (#732) -- Support also support releases in ci pipeline -- Introduce typed object for oauth2 provisioning -- Add documentation -- Add test case -- Add client to omejdn -- add hydra deployment -- Configure dynamically HTTP Receiver callback endpoints. (#685) -- cp-adapter : code review, rollbacke name change (#664) -- Feature/cp adapter task 355 356 357 (#621) -- Add Validity Mapping in ContractDefinitionStepDefs class -- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps -- Add validity attribute in class ContractDefinition -- Add Validity Mapping in ContractDefinitionStepDefs class -- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps -- Add validity attribute in class ContractDefinition -- Local TXDC Setup Documentation (#618) -- Feature: Sftp Provisioner and Client (#554) +- Add contract id to data source http call (#732) +- Support also support releases in ci pipeline +- Introduce typed object for oauth2 provisioning +- Add documentation +- Add test case +- Add client to omejdn +- add hydra deployment +- Configure dynamically HTTP Receiver callback endpoints. (#685) +- cp-adapter : code review, rollbacke name change (#664) +- Feature/cp adapter task 355 356 357 (#621) +- Add Validity Mapping in ContractDefinitionStepDefs class +- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps +- Add validity attribute in class ContractDefinition +- Add Validity Mapping in ContractDefinitionStepDefs class +- Add feature and create SendAnOfferwithoutConstraints method in class negotiationSteps +- Add validity attribute in class ContractDefinition +- Local TXDC Setup Documentation (#618) +- Feature: Sftp Provisioner and Client (#554) ### Changed -- Support horizontal edc scaling in cp adapter extension (#678) -- Use upstream jackson version (#741) -- Replace provision-oauth2 with data-plane-http-oauth2 -- docs: Update sample documentation (#671) -- chore: Disable build ci pipeline if just docu was updated (#705) -- Increase trivy timeout -- Remove not useful anymore custom-jsonld extension (#683) -- update setup docu (#654) -- remove trailing slash (#652) -- update alpine from 3.17.0 to 3.17.1 for controlplane-memory-hashicorp-vault (#665) -- Feature/set charts deprecated (#628) -- update setup docu (#627) -- Feature/update txdc deployment downward capabilities (#625) -- remove git submodule (#619) -- Feature/update postman (#624) -- update control plane docu (#623) -- update postgresql version in Chart.yaml supporting-infrastructure (#622) -- update link to edc logo in README.md (#612) -- update description of supporting infrastructure deployment (#616) +- Support horizontal edc scaling in cp adapter extension (#678) +- Use upstream jackson version (#741) +- Replace provision-oauth2 with data-plane-http-oauth2 +- docs: Update sample documentation (#671) +- chore: Disable build ci pipeline if just docu was updated (#705) +- Increase trivy timeout +- Remove not useful anymore custom-jsonld extension (#683) +- update setup docu (#654) +- remove trailing slash (#652) +- update alpine from 3.17.0 to 3.17.1 for controlplane-memory-hashicorp-vault (#665) +- Feature/set charts deprecated (#628) +- update setup docu (#627) +- Feature/update txdc deployment downward capabilities (#625) +- remove git submodule (#619) +- Feature/update postman (#624) +- update control plane docu (#623) +- update postgresql version in Chart.yaml supporting-infrastructure (#622) +- update link to edc logo in README.md (#612) +- update description of supporting infrastructure deployment (#616) ### Fixed -- bugfix: Fix slow AES encryption (#746) -- Fix typo in tractusx-connector values.yaml comment -- Fix not working docu link in README.md -- Fix typo in control-plane adapter README +- bugfix: Fix slow AES encryption (#746) +- Fix typo in tractusx-connector values.yaml comment +- Fix not working docu link in README.md +- Fix typo in control-plane adapter README ### Dependency updates -- Bump EDC to 20220220 (#767) -- Bump alpine (#749) -- Bump alpine (#750) -- Bump alpine (#752) -- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#753) -- Bump maven-deploy-plugin from 3.0.0 to 3.1.0 (#735) -- Bump actions/setup-java from 3.9.0 to 3.10.0 (#730) -- Bump s3 from 2.19.33 to 2.20.0 -- Bump s3 from 2.19.27 to 2.19.33 -- Bump jaxb-runtime from 4.0.1 to 4.0.2 -- Bump spotless-maven-plugin from 2.31.0 to 2.32.0 -- Bump postgresql from 42.5.1 to 42.5.3 -- Bump nimbus-jose-jwt from 9.30 to 9.30.1 -- Bump lombok from 1.18.24 to 1.18.26 -- Bump flyway-core from 9.12.0 to 9.14.1 -- Bump jackson-bom from 2.14.0-rc2 to 2.14.2 -- Bump cucumber.version from 7.11.0 to 7.11.1 -- Bump azure-sdk-bom from 1.2.8 to 1.2.9 -- Bump mockito-bom from 5.0.0 to 5.1.1 -- Bump edc version to 0.0.1-20230131-SNAPSHOT -- Bump s3 from 2.19.18 to 2.19.27 -- Bump docker/build-push-action from 3 to 4 -- Bump nimbus-jose-jwt from 9.29 to 9.30 -- Bump spotless-maven-plugin from 2.30.0 to 2.31.0 -- Bump nimbus-jose-jwt from 9.28 to 9.29 -- Bump mockito-bom from 4.11.0 to 5.0.0 -- Bump edc version to 0.0.1-20230125-SNAPSHOT -- Bump flyway-core from 9.11.0 to 9.12.0 -- Bump s3 from 2.19.15 to 2.19.18 (#684) -- Bump mikefarah/yq from 4.30.6 to 4.30.8 (#682) -- Bump spotless-maven-plugin from 2.29.0 to 2.30.0 -- Bump edc version to 0.0.1-20230115-SNAPSHOT -- Bump cucumber.version from 7.10.1 to 7.11.0 (#672) -- Bump maven-dependency-plugin from 3.4.0 to 3.5.0 (#669) -- Bump s3 from 2.19.11 to 2.19.15 (#668) -- Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#670) -- Bump edc version to 0.0.1-20230109-SNAPSHOT (#666) -- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#659) -- Bump alpine in /edc-dataplane/edc-dataplane-azure-vault/src/main/docker (#660) -- Bump alpine (#658) -- Bump alpine (#661) -- Bump alpine (#662) -- Bump azure/setup-kubectl from 3.1 to 3.2 (#655) -- Bump junit-bom from 5.9.1 to 5.9.2 (#657) -- Bump s3 from 2.19.2 to 2.19.11 (#648) -- Bump actions/checkout from 3.2.0 to 3.3.0 (#647) -- Bump flyway-core from 9.10.2 to 9.11.0 (#646) -- Bump spotless-maven-plugin from 2.28.0 to 2.29.0 (#641) -- Bump mockito-bom from 4.10.0 to 4.11.0 (#637) -- Bump flyway-core from 9.10.1 to 9.10.2 (#632) -- Bump s3 from 2.19.1 to 2.19.2 (#631) -- Bump s3 from 2.18.41 to 2.19.1 (#626) -- Bump mikefarah/yq from 4.30.5 to 4.30.6 (#613) -- Bump cucumber.version from 7.10.0 to 7.10.1 (#614) -- Bump s3 from 2.18.40 to 2.18.41 (#615) -- Bump azure/setup-helm from 3.4 to 3.5 (#596) -- Bump actions/checkout from 3.1.0 to 3.2.0 (#598) -- Bump mockito-bom from 4.9.0 to 4.10.0 (#607) -- Bump s3 from 2.18.39 to 2.18.40 (#609) -- Bump flyway-core from 9.10.0 to 9.10.1 (#610) -- Bump actions/setup-java from 3.8.0 to 3.9.0 (#605) -- Bump s3 from 2.18.35 to 2.18.39 (#606) +- Bump EDC to 20220220 (#767) +- Bump alpine (#749) +- Bump alpine (#750) +- Bump alpine (#752) +- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#753) +- Bump maven-deploy-plugin from 3.0.0 to 3.1.0 (#735) +- Bump actions/setup-java from 3.9.0 to 3.10.0 (#730) +- Bump s3 from 2.19.33 to 2.20.0 +- Bump s3 from 2.19.27 to 2.19.33 +- Bump jaxb-runtime from 4.0.1 to 4.0.2 +- Bump spotless-maven-plugin from 2.31.0 to 2.32.0 +- Bump postgresql from 42.5.1 to 42.5.3 +- Bump nimbus-jose-jwt from 9.30 to 9.30.1 +- Bump lombok from 1.18.24 to 1.18.26 +- Bump flyway-core from 9.12.0 to 9.14.1 +- Bump jackson-bom from 2.14.0-rc2 to 2.14.2 +- Bump cucumber.version from 7.11.0 to 7.11.1 +- Bump azure-sdk-bom from 1.2.8 to 1.2.9 +- Bump mockito-bom from 5.0.0 to 5.1.1 +- Bump edc version to 0.0.1-20230131-SNAPSHOT +- Bump s3 from 2.19.18 to 2.19.27 +- Bump docker/build-push-action from 3 to 4 +- Bump nimbus-jose-jwt from 9.29 to 9.30 +- Bump spotless-maven-plugin from 2.30.0 to 2.31.0 +- Bump nimbus-jose-jwt from 9.28 to 9.29 +- Bump mockito-bom from 4.11.0 to 5.0.0 +- Bump edc version to 0.0.1-20230125-SNAPSHOT +- Bump flyway-core from 9.11.0 to 9.12.0 +- Bump s3 from 2.19.15 to 2.19.18 (#684) +- Bump mikefarah/yq from 4.30.6 to 4.30.8 (#682) +- Bump spotless-maven-plugin from 2.29.0 to 2.30.0 +- Bump edc version to 0.0.1-20230115-SNAPSHOT +- Bump cucumber.version from 7.10.1 to 7.11.0 (#672) +- Bump maven-dependency-plugin from 3.4.0 to 3.5.0 (#669) +- Bump s3 from 2.19.11 to 2.19.15 (#668) +- Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#670) +- Bump edc version to 0.0.1-20230109-SNAPSHOT (#666) +- Bump alpine in /edc-controlplane/edc-runtime-memory/src/main/docker (#659) +- Bump alpine in /edc-dataplane/edc-dataplane-azure-vault/src/main/docker (#660) +- Bump alpine (#658) +- Bump alpine (#661) +- Bump alpine (#662) +- Bump azure/setup-kubectl from 3.1 to 3.2 (#655) +- Bump junit-bom from 5.9.1 to 5.9.2 (#657) +- Bump s3 from 2.19.2 to 2.19.11 (#648) +- Bump actions/checkout from 3.2.0 to 3.3.0 (#647) +- Bump flyway-core from 9.10.2 to 9.11.0 (#646) +- Bump spotless-maven-plugin from 2.28.0 to 2.29.0 (#641) +- Bump mockito-bom from 4.10.0 to 4.11.0 (#637) +- Bump flyway-core from 9.10.1 to 9.10.2 (#632) +- Bump s3 from 2.19.1 to 2.19.2 (#631) +- Bump s3 from 2.18.41 to 2.19.1 (#626) +- Bump mikefarah/yq from 4.30.5 to 4.30.6 (#613) +- Bump cucumber.version from 7.10.0 to 7.10.1 (#614) +- Bump s3 from 2.18.40 to 2.18.41 (#615) +- Bump azure/setup-helm from 3.4 to 3.5 (#596) +- Bump actions/checkout from 3.1.0 to 3.2.0 (#598) +- Bump mockito-bom from 4.9.0 to 4.10.0 (#607) +- Bump s3 from 2.18.39 to 2.18.40 (#609) +- Bump flyway-core from 9.10.0 to 9.10.1 (#610) +- Bump actions/setup-java from 3.8.0 to 3.9.0 (#605) +- Bump s3 from 2.18.35 to 2.18.39 (#606) ## [0.1.6] - 2023-02-20 ### Fixed -- SQL leakage issue -- Catalog pagination +- SQL leakage issue +- Catalog pagination ## [0.1.5] - 2023-02-13 ### Fixed -- Use patched EDC version: 0.0.1-20220922.2-SNAPSHOT to fix catalog pagination bug -- Data Encryption extension: fixed usage of a blocking algorithm +- Use patched EDC version: 0.0.1-20220922.2-SNAPSHOT to fix catalog pagination bug +- Data Encryption extension: fixed usage of a blocking algorithm ## [0.1.2] - 2022-09-30 ### Added -- Introduced DEPENDENCIES file +- Introduced DEPENDENCIES file ### Changed -- Moved helm charts from `deployment/helm` to `charts` -- Replaced distroless image with alpine in all docker images -- Update EDC commit to `740c100ac162bc41b1968c232ad81f7d739aefa9` +- Moved helm charts from `deployment/helm` to `charts` +- Replaced distroless image with alpine in all docker images +- Update EDC commit to `740c100ac162bc41b1968c232ad81f7d739aefa9` ## [0.1.1] - 2022-09-04 @@ -194,17 +212,17 @@ connector. [documentation](/docs/migration/Version_0.1.0_0.1.1.md). ### Added -- Control-Plane Extension ([cx-oauth2](/edc-extensions/cx-oauth2/README.md)) +- Control-Plane Extension ([cx-oauth2](/edc-extensions/cx-oauth2/README.md)) ### Changed -- Introduced git submodule to import EDC dependencies (instead of snapshot- or milestone artifact) -- Helm Charts: TLS secret name is now configurable +- Introduced git submodule to import EDC dependencies (instead of snapshot- or milestone artifact) +- Helm Charts: TLS secret name is now configurable ### Fixed -- Connectors with Azure Vault extension are now starting - again [link](https://github.com/eclipse-edc/Connector/issues/1892) +- Connectors with Azure Vault extension are now starting + again [link](https://github.com/eclipse-edc/Connector/issues/1892) ## [0.1.0] - 2022-08-19 @@ -213,74 +231,74 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ### Added -- Control-Plane - extension ([data-plane-selector-client](https://github.com/eclipse-edc/Connector/tree/v0.0.1-milestone-5/extensions/data-plane-selector/selector-client)) - - run the EDC with multiple data planes at once -- Control-Plane extension ([dataplane-selector-configuration](edc-extensions/dataplane-selector-configuration)) - - add data plane instances to the control plane by configuration -- Data-Plane - extension ([s3-data-plane](https://github.com/eclipse-edc/Connector/tree/main/extensions/aws/data-plane-s3)) - - transfer from and to AWS S3 buckets -- Control-Plane extension ([data-encryption](edc-extensions/data-encryption)) - - Data-Plane authentication attribute transmitted during data-plane-transfer can be encrypted symmetrically (AES) +- Control-Plane + extension ([data-plane-selector-client](https://github.com/eclipse-edc/Connector/tree/v0.0.1-milestone-5/extensions/data-plane-selector/selector-client)) + - run the EDC with multiple data planes at once +- Control-Plane extension ([dataplane-selector-configuration](edc-extensions/dataplane-selector-configuration)) + - add data plane instances to the control plane by configuration +- Data-Plane + extension ([s3-data-plane](https://github.com/eclipse-edc/Connector/tree/main/extensions/aws/data-plane-s3)) + - transfer from and to AWS S3 buckets +- Control-Plane extension ([data-encryption](edc-extensions/data-encryption)) + - Data-Plane authentication attribute transmitted during data-plane-transfer can be encrypted symmetrically (AES) ### Changed -- Update setting name (`edc.dataplane.token.validation.endpoint` -> `edc.dataplane.token.validation.endpoint`) -- EDC has been updated to - version [0.0.1-20220818-SNAPSHOT](https://oss.sonatype.org/#nexus-search;gav~org.eclipse.dataspaceconnector~~0.0.1-20220818-SNAPSHOT~~) - - implications to the behavior of the connector have been covered in - the [corresponding migration guide](docs/migration/Version_0.0.x_0.1.x.md) +- Update setting name (`edc.dataplane.token.validation.endpoint` -> `edc.dataplane.token.validation.endpoint`) +- EDC has been updated to + version [0.0.1-20220818-SNAPSHOT](https://oss.sonatype.org/#nexus-search;gav~org.eclipse.dataspaceconnector~~0.0.1-20220818-SNAPSHOT~~) - + implications to the behavior of the connector have been covered in + the [corresponding migration guide](docs/migration/Version_0.0.x_0.1.x.md) ### Fixed -- Contract-Offer-Receiving-Connectors must also pass the ContractPolicy of the ContractDefinition before receiving - offers([issue](https://github.com/eclipse-edc/Connector/issues/1331)) -- Deletion of Asset becomes impossible when Contract Negotiation - exists([issue](https://github.com/eclipse-edc/Connector/issues/1403)) -- Deletion of Policy becomes impossible when Contract Definition - exists([issue](https://github.com/eclipse-edc/Connector/issues/1410)) +- Contract-Offer-Receiving-Connectors must also pass the ContractPolicy of the ContractDefinition before receiving + offers([issue](https://github.com/eclipse-edc/Connector/issues/1331)) +- Deletion of Asset becomes impossible when Contract Negotiation + exists([issue](https://github.com/eclipse-edc/Connector/issues/1403)) +- Deletion of Policy becomes impossible when Contract Definition + exists([issue](https://github.com/eclipse-edc/Connector/issues/1410)) ## [0.0.6] - 2022-07-29 ### Fixed -- Fixes [release 0.0.5](https://github.com/eclipse-tractusx/tractusx-edc/releases/tag/0.0.5), which introduced classpath - issues due to usage of [net.jodah:failsafe:2.4.3](https://search.maven.org/artifact/net.jodah/failsafe/2.4.3/jar) - library +- Fixes [release 0.0.5](https://github.com/eclipse-tractusx/tractusx-edc/releases/tag/0.0.5), which introduced classpath + issues due to usage of [net.jodah:failsafe:2.4.3](https://search.maven.org/artifact/net.jodah/failsafe/2.4.3/jar) + library ## [0.0.5] - 2022-07-28 ### Added -- EDC Health Checks for HashiCorp Vault +- EDC Health Checks for HashiCorp Vault ### Changed -- BusinessPartnerNumber constraint supports List structure -- Helm: Confidential EDC settings can be set using k8s secrets -- HashiCorp Vault API path configurable +- BusinessPartnerNumber constraint supports List structure +- Helm: Confidential EDC settings can be set using k8s secrets +- HashiCorp Vault API path configurable ## [0.0.4] - 2022-06-27 ### Added -- HashiCorp Vault Extension -- Control Plane with HashiCorp Vault and PostgreSQL support +- HashiCorp Vault Extension +- Control Plane with HashiCorp Vault and PostgreSQL support ### Changed -- Release Workflow now publishes EDC Extensions as Maven Artifacts +- Release Workflow now publishes EDC Extensions as Maven Artifacts ### Fixed -- [#1515](https://github.com/eclipse-edc/Connector/issues/1515) SQL: Connector sends out 50 - contract offers max. +- [#1515](https://github.com/eclipse-edc/Connector/issues/1515) SQL: Connector sends out 50 + contract offers max. ### Removed -- CosmosDB Control Plane -- Control API Extension for all Control Planes +- CosmosDB Control Plane +- Control API Extension for all Control Planes ## [0.0.3] - 2022-05-23 @@ -288,7 +306,9 @@ corresponding [documentation](/docs/migration/Version_0.0.x_0.1.x.md). ## [0.0.1] - 2022-05-13 -[Unreleased]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.3...HEAD +[Unreleased]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.4.0...HEAD + +[0.4.0]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.3...0.4.0 [0.3.3]: https://github.com/eclipse-tractusx/tractusx-edc/compare/0.3.2...0.3.3 diff --git a/charts/tractusx-connector-azure-vault/Chart.yaml b/charts/tractusx-connector-azure-vault/Chart.yaml index 1fa9d3e3a..3169ba8e3 100644 --- a/charts/tractusx-connector-azure-vault/Chart.yaml +++ b/charts/tractusx-connector-azure-vault/Chart.yaml @@ -40,12 +40,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.4 +version: 0.4.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.3.4" +appVersion: "0.4.0" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector diff --git a/charts/tractusx-connector-azure-vault/README.md b/charts/tractusx-connector-azure-vault/README.md index 003e75320..ec46cbe07 100644 --- a/charts/tractusx-connector-azure-vault/README.md +++ b/charts/tractusx-connector-azure-vault/README.md @@ -1,6 +1,6 @@ # tractusx-connector-azure-vault -![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) +![Version: 0.4.0](https://img.shields.io/badge/Version-0.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.0](https://img.shields.io/badge/AppVersion-0.4.0-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and Azure KeyVault are included. @@ -38,7 +38,7 @@ Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector-azure-vault --version 0.3.4 \ +helm install my-release tractusx-edc/tractusx-connector-azure-vault --version 0.4.0 \ -f /tractusx-connector-azure-vault-test.yaml \ --set vault.azure.name=$AZURE_VAULT_NAME \ --set vault.azure.client=$AZURE_CLIENT_ID \ diff --git a/charts/tractusx-connector-memory/Chart.yaml b/charts/tractusx-connector-memory/Chart.yaml index 354d8a7a9..8986d3072 100644 --- a/charts/tractusx-connector-memory/Chart.yaml +++ b/charts/tractusx-connector-memory/Chart.yaml @@ -34,12 +34,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.4 +version: 0.4.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.3.4" +appVersion: "0.4.0" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 654b28900..2a7a25af5 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -1,6 +1,6 @@ # tractusx-connector-memory -![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) +![Version: 0.4.0](https://img.shields.io/badge/Version-0.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.0](https://img.shields.io/badge/AppVersion-0.4.0-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector based on memory. Please only use this for development or testing purposes, never in production workloads! @@ -32,7 +32,7 @@ Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector-memory --version 0.3.4 \ +helm install my-release tractusx-edc/tractusx-connector-memory --version 0.4.0 \ -f /example.yaml \ --set vault.secrets="daps-cert:$DAPS_CERT;daps-key:$DAPS_KEY" \ ``` diff --git a/charts/tractusx-connector/Chart.yaml b/charts/tractusx-connector/Chart.yaml index b13ff5d01..f20757da3 100644 --- a/charts/tractusx-connector/Chart.yaml +++ b/charts/tractusx-connector/Chart.yaml @@ -40,12 +40,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.3.4 +version: 0.4.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.3.4" +appVersion: "0.4.0" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index 436d347d6..b2c9f8e2d 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -1,6 +1,6 @@ # tractusx-connector -![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.3.4](https://img.shields.io/badge/AppVersion-0.3.4-informational?style=flat-square) +![Version: 0.4.0](https://img.shields.io/badge/Version-0.4.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.0](https://img.shields.io/badge/AppVersion-0.4.0-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and HashiCorp Vault are included. @@ -37,7 +37,7 @@ Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector-azure-vault --version 0.3.4 \ +helm install my-release tractusx-edc/tractusx-connector-azure-vault --version 0.4.0 \ -f /tractusx-connector-test.yaml ``` diff --git a/core/edr-cache-core/build.gradle.kts b/core/edr-cache-core/build.gradle.kts new file mode 100644 index 000000000..0c1e5474d --- /dev/null +++ b/core/edr-cache-core/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) + implementation(libs.edc.config.filesystem) + implementation(libs.edc.util) + + implementation(project(":spi:edr-cache-spi")) +} + diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java new file mode 100644 index 000000000..5de7e78a5 --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/EdrCacheCoreExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.edr.core.defaults.InMemoryEndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +/** + * Registers default services for the EDR cache. + */ +@Extension(value = EdrCacheCoreExtension.NAME) +public class EdrCacheCoreExtension implements ServiceExtension { + static final String NAME = "EDR Cache Core"; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Provider(isDefault = true) + public EndpointDataReferenceCache edrCache(ServiceExtensionContext context) { + return new InMemoryEndpointDataReferenceCache(); + } + +} diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java new file mode 100644 index 000000000..d396b4170 --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCache.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.util.concurrency.LockManager; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; +import static org.eclipse.edc.spi.result.StoreResult.notFound; +import static org.eclipse.edc.spi.result.StoreResult.success; + +/** + * An in-memory, threadsafe implementation of the cache. + */ +public class InMemoryEndpointDataReferenceCache implements EndpointDataReferenceCache { + private final LockManager lockManager; + + private final Map> entriesByAssetId; + private final Map entriesByEdrId; + private final Map edrsByTransferProcessId; + + public InMemoryEndpointDataReferenceCache() { + lockManager = new LockManager(new ReentrantReadWriteLock()); + entriesByAssetId = new HashMap<>(); + entriesByEdrId = new HashMap<>(); + edrsByTransferProcessId = new HashMap<>(); + } + + @Override + public @Nullable EndpointDataReference resolveReference(String transferProcessId) { + return lockManager.readLock(() -> edrsByTransferProcessId.get(transferProcessId)); + } + + @Override + @NotNull + public List referencesForAsset(String assetId) { + var entries = entriesByAssetId.get(assetId); + if (entries == null) { + return emptyList(); + } + return entries.stream().map(e -> resolveReference(e.getTransferProcessId())).filter(Objects::nonNull).collect(toList()); + } + + @Override + @NotNull + public List entriesForAsset(String assetId) { + return lockManager.readLock(() -> entriesByAssetId.getOrDefault(assetId, emptyList())); + } + + @Override + public void save(EndpointDataReferenceEntry entry, EndpointDataReference edr) { + lockManager.writeLock(() -> { + entriesByEdrId.put(edr.getId(), entry); + var list = entriesByAssetId.computeIfAbsent(entry.getAssetId(), k -> new ArrayList<>()); + list.add(entry); + + edrsByTransferProcessId.put(entry.getTransferProcessId(), edr); + return null; + }); + } + + @Override + public StoreResult deleteByTransferProcessId(String id) { + return lockManager.writeLock(() -> { + var edr = edrsByTransferProcessId.remove(id); + if (edr == null) { + return notFound("EDR entry not found for id: " + id); + } + var entry = entriesByEdrId.get(edr.getId()); + var entries = entriesByAssetId.get(entry.getAssetId()); + entries.remove(entry); + if (entries.isEmpty()) { + entriesByAssetId.remove(entry.getAssetId()); + } + return success(entry); + }); + } +} diff --git a/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java new file mode 100644 index 000000000..3a9ae9ebd --- /dev/null +++ b/core/edr-cache-core/src/main/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; + +/** + * A wrapper to persist {@link EndpointDataReferenceEntry}s and {@link EndpointDataReference}s. + */ +public class PersistentCacheEntry { + private EndpointDataReferenceEntry edrEntry; + private EndpointDataReference edr; + + public PersistentCacheEntry(@JsonProperty("edrEntry") EndpointDataReferenceEntry edrEntry, @JsonProperty("edr") EndpointDataReference edr) { + this.edrEntry = edrEntry; + this.edr = edr; + } + + public EndpointDataReferenceEntry getEdrEntry() { + return edrEntry; + } + + public EndpointDataReference getEdr() { + return edr; + } +} diff --git a/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..67f4a6027 --- /dev/null +++ b/core/edr-cache-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.edr.core.EdrCacheCoreExtension diff --git a/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java new file mode 100644 index 000000000..c2dab4afc --- /dev/null +++ b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/InMemoryEndpointDataReferenceCacheTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.Test; + +import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; + +class InMemoryEndpointDataReferenceCacheTest { + private static final String TRANSFER_PROCESS_ID = "tp1"; + private static final String ASSET_ID = "asset1"; + private static final String EDR_ID = "edr1"; + + private InMemoryEndpointDataReferenceCache cache = new InMemoryEndpointDataReferenceCache(); + + @Test + @SuppressWarnings("DataFlowIssue") + void verify_operations() { + var edr = EndpointDataReference.Builder.newInstance(). + endpoint("http://test.com") + .id(EDR_ID) + .authCode("11111") + .authKey("authentication").build(); + + var entry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId(ASSET_ID) + .agreementId(randomUUID().toString()) + .transferProcessId(TRANSFER_PROCESS_ID) + .build(); + + cache.save(entry, edr); + + assertThat(cache.resolveReference(TRANSFER_PROCESS_ID).getId()).isEqualTo(EDR_ID); + + var edrs = cache.referencesForAsset(ASSET_ID); + assertThat(edrs.size()).isEqualTo(1); + assertThat(edrs.get((0)).getId()).isEqualTo(EDR_ID); + + var entries = cache.entriesForAsset(ASSET_ID); + assertThat(entries.size()).isEqualTo(1); + assertThat(entries.get((0)).getAssetId()).isEqualTo(ASSET_ID); + + assertThat(cache.deleteByTransferProcessId(TRANSFER_PROCESS_ID).succeeded()).isTrue(); + + assertThat(cache.entriesForAsset(ASSET_ID)).isEmpty(); + assertThat(cache.resolveReference(TRANSFER_PROCESS_ID)).isNull(); + } +} diff --git a/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java new file mode 100644 index 000000000..764afb33b --- /dev/null +++ b/core/edr-cache-core/src/test/java/org/eclipse/tractusx/edc/edr/core/defaults/PersistentCacheEntryTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.core.defaults; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.Test; + +import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; + +class PersistentCacheEntryTest { + + @Test + void verify_serializeDeserialize() throws JsonProcessingException { + var mapper = new ObjectMapper(); + + var edr = EndpointDataReference.Builder.newInstance(). + endpoint("http://test.com") + .id(randomUUID().toString()) + .authCode("11111") + .authKey("authentication").build(); + + var edrEntry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId(randomUUID().toString()) + .agreementId(randomUUID().toString()) + .transferProcessId(randomUUID().toString()) + .build(); + + var serialized = mapper.writeValueAsString(new PersistentCacheEntry(edrEntry, edr)); + + var deserialized = mapper.readValue(serialized, PersistentCacheEntry.class); + + assertThat(deserialized.getEdrEntry()).isNotNull(); + assertThat(deserialized.getEdr()).isNotNull(); + } + + +} diff --git a/docs/development/Run-business-tests-local.md b/docs/development/Run-business-tests-local.md deleted file mode 100644 index cab17c6c2..000000000 --- a/docs/development/Run-business-tests-local.md +++ /dev/null @@ -1,161 +0,0 @@ -# Run and debug Business-Tests local within IDE - -Prerequisites: - -- You need a local kubernetes cluster to install the services (Docker Desktop is recommended). -- You need kubectl and helm command line tools installed. - -## 1. Build all modules with maven and produce docker images - -```shell -./gradlew dockerize -``` - -## 2. Install the all-in-one supporting infrastructure environment (Daps, Vault, PostgreSql, Minio, Backend-Service) - -```shel -helm install infrastructure edc-tests/src/main/resources/deployment/helm/supporting-infrastructure -n business-tests --dependency-update --create-namespace -``` - -To access the PostgreSql databases you could use following kubectl port forwardings: - -```shell -kubectl port-forward plato-postgresql-0 -n business-tests 5555:5432 -kubectl port-forward sokrates-postgresql-0 -n business-tests 6666:5432 -``` - -Please use the same ports later for your environment variables. - -## 3. Install Plato as provider EDC - -```shell -helm install plato charts/tractusx-connector -n business-tests --create-namespace \ - --set fullnameOverride=plato \ - --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.data.authKey=password \ - --set controlplane.image.pullPolicy=Never \ - --set controlplane.image.tag=latest \ - --set controlplane.image.repository=edc-controlplane-postgresql-hashicorp-vault \ - --set dataplane.image.tag=latest \ - --set dataplane.image.pullPolicy=Never \ - --set dataplane.image.repository=edc-dataplane-hashicorp-vault \ - --set controlplane.debug.enabled=true \ - --set controlplane.suspendOnStart=false \ - --set postgresql.enabled=true \ - --set postgresql.username=user \ - --set postgresql.password=password \ - --set postgresql.jdbcUrl=jdbc:postgresql://plato-postgresql:5432/edc \ - --set vault.hashicorp.enabled=true \ - --set vault.hashicorp.url=http://vault:8200 \ - --set vault.hashicorp.token=root \ - --set vault.secretNames.transferProxyTokenSignerPublicKey=plato/daps/my-plato-daps-crt \ - --set vault.secretNames.transferProxyTokenSignerPrivateKey=plato/daps/my-plato-daps-key \ - --set vault.secretNames.transferProxyTokenEncryptionAesKey=plato/data-encryption-aes-keys \ - --set vault.secretNames.dapsPrivateKey=plato/daps/my-plato-daps-key \ - --set vault.secretNames.dapsPublicKey=plato/daps/my-plato-daps-crt \ - --set daps.url=http://ids-daps:4567 \ - --set daps.clientId=99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23 \ - --set dataplane.aws.endpointOverride=http://minio:9000 \ - --set dataplane.aws.secretAccessKey=platoqwerty123 \ - --set dataplane.aws.accessKeyId=platoqwerty123 \ - --set backendService.httpProxyTokenReceiverUrl=http://backend:8080 \ - --wait-for-jobs --timeout=120s -``` - -## 4. Install Socrates as consumer EDC - -```shell -helm install sokrates charts/tractusx-connector -n business-tests --create-namespace \ - --set fullnameOverride=sokrates \ - --set controlplane.service.type=NodePort \ - --set controlplane.endpoints.data.authKey=password \ - --set controlplane.image.pullPolicy=Never \ - --set controlplane.image.tag=latest \ - --set controlplane.image.repository=edc-controlplane-postgresql-hashicorp-vault \ - --set dataplane.image.tag=latest \ - --set dataplane.image.pullPolicy=Never \ - --set dataplane.image.repository=edc-dataplane-hashicorp-vault \ - --set controlplane.debug.enabled=true \ - --set controlplane.suspendOnStart=false \ - --set postgresql.enabled=true \ - --set postgresql.username=user \ - --set postgresql.password=password \ - --set postgresql.jdbcUrl=jdbc:postgresql://sokrates-postgresql:5432/edc \ - --set vault.hashicorp.enabled=true \ - --set vault.hashicorp.url=http://vault:8200 \ - --set vault.hashicorp.token=root \ - --set vault.secretNames.transferProxyTokenSignerPublicKey=sokrates/daps/my-sokrates-daps-crt \ - --set vault.secretNames.transferProxyTokenSignerPrivateKey=sokrates/daps/my-sokrates-daps-key \ - --set vault.secretNames.transferProxyTokenEncryptionAesKey=sokrates/data-encryption-aes-keys \ - --set vault.secretNames.dapsPrivateKey=sokrates/daps/my-sokrates-daps-key \ - --set vault.secretNames.dapsPublicKey=sokrates/daps/my-sokrates-daps-crt \ - --set daps.url=http://ids-daps:4567 \ - --set daps.clientId=E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65 \ - --set dataplane.aws.endpointOverride=http://minio:9000 \ - --set dataplane.aws.secretAccessKey=sokratesqwerty123 \ - --set dataplane.aws.accessKeyId=sokratesqwerty123 \ - --set backendService.httpProxyTokenReceiverUrl=http://backend:8080 \ - --wait-for-jobs --timeout=120s -``` - -## 5. Set environment variables and run configuration in IDE - -You can create a run configuration in IntelliJ like bellow screenshot and copy/paste the whole set of environments variables if you use ";" after each line. - -![Example run config](run-config.png) - -```shell -PLATO_BACKEND_SERVICE_BACKEND_API_URL=http://localhost:; -PLATO_DATA_MANAGEMENT_API_AUTH_KEY=password; -PLATO_DATA_MANAGEMENT_URL=http://localhost:/data; -PLATO_DATA_PLANE_URL=foo; -PLATO_DATABASE_PASSWORD=password; -PLATO_DATABASE_URL=jdbc:postgresql://localhost:5555/edc; -PLATO_DATABASE_USER=user; -PLATO_IDS_URL=http://plato-controlplane:8084/api/v1/ids; -PLATO_AWS_SECRET_ACCESS_KEY=platoqwerty123; -PLATO_AWS_ACCESS_KEY_ID=platoqwerty123; -SOKRATES_BACKEND_SERVICE_BACKEND_API_URL=http://localhost:; -SOKRATES_BACKEND_URL=http://localhost:; -SOKRATES_DATA_MANAGEMENT_API_AUTH_KEY=password; -SOKRATES_DATA_MANAGEMENT_URL=http://localhost:/data; -SOKRATES_DATA_PLANE_URL=foo; -SOKRATES_DATABASE_PASSWORD=password; -SOKRATES_DATABASE_URL=jdbc:postgresql://localhost:6666/edc; -SOKRATES_DATABASE_USER=user; -SOKRATES_IDS_URL=http://sokrates-controlplane:8084/api/v1/ids; -SOKRATES_AWS_SECRET_ACCESS_KEY=sokratesqwerty123; -SOKRATES_AWS_ACCESS_KEY_ID=sokratesqwerty123; -EDC_AWS_ENDPOINT_OVERRIDE=http://localhost:32000 -``` - -The services are using NodePort to expose the endpoints therefore the ports are not fix and needs to be determined after each deployment. -To determine the current ports you can use the following kubectl command: - -```shell -kubectl get svc -n business-tests -o go-template='{{range .items}}{{ $save := . }}{{range.spec.ports}}{{if .nodePort}}{{$save.metadata.namespace}}{{"/"}}{{$save.metadata.name}}{{" - "}}{{.name}}{{": "}}{{.nodePort}}{{"("}}{{.port}}{{")"}}{{"\n"}}{{end}}{{end}}{{end}}' -``` - -This will return all NodePorts which are available in business-tests namespace where you can pick the ports to use in your environment variables. -Now you are able to run it in IDE either as normal "Run" mode or in "Debug" mode where you can debug the business-tests by setting debugging points. - -Example of mapping to environment variables needed for the business tests: - -```shell -business-tests/plato-controlplane - data: 30955(8081) -> PLATO_DATA_MANAGEMENT_URL=http://localhost:30955/data; -business-tests/sokrates-controlplane - data: 30538(8081) -> SOKRATES_DATA_MANAGEMENT_URL=http://localhost:30538/data; -business-tests/backend - backend: 30556(8081) -> SOKRATES_BACKEND_SERVICE_BACKEND_API_URL= http://localhost:30556 -``` - -## 6. Update your components - -Once everything is installed you just need to update your services when you have a new image. - -```shell -helm upgrade plato charts/tractusx-connector --recreate-pods -helm upgrade sokrates charts/tractusx-connector --recreate-pods -``` - -## 7. Tips - -If you use the kubernetes within Docker Desktop you have direct access to the images which you have created with Docker Desktop they are using the same docker daemon. So you don't need to transfer it in your k8s cluster. diff --git a/docs/migration/Version_0.3.4_0.4.0.md b/docs/migration/Version_0.3.4_0.4.0.md new file mode 100644 index 000000000..cca287921 --- /dev/null +++ b/docs/migration/Version_0.3.4_0.4.0.md @@ -0,0 +1,29 @@ +# Migration from 0.3.3 to 0.3.4 + +## Switching to DSP + +The Eclipse Dataspace Connector protocol recently moved its protocol implementation from IDS to DSP as of +version `0.0.1-milestone-9`. +From the Tractus-X EDC perspective this causes breaking changes in the following areas: + +- the Management API: because DSP uses JSON-LD, all Management API endpoints had to be adapted as well to reflect that. + The old Management API is now deprecated and is **not** tested for compliance. Please upgrade using the `/v2/` path + for every endpoint, e.g. `/management/v2/assets`. Please also refer to + the [EDC OpenAPI spec](https://app.swaggerhub.com/apis/eclipse-edc-bot/management-api/0.0.1-SNAPSHOT#/). +- modules: all `*ids*` modules are deprecated and cannot be used anymore. Please migrate over + to `org.eclipse.edc:dsp:0.0.1-milestone-9`. + +**Please note that this is not a complete documentation of the DSP Protocol, please refer to +the [official documentation](https://docs.internationaldataspaces.org/dataspace-protocol/overview/readme)** + +## Removal of the Business Tests + +The business tests were removed from the code base, because all the ever tested is already by other tests, specifically +the JUnit-based tests, deployment tests, or other tests that are already done upstream in EDC. + +The Business tests were brittle, consumed a lot of resources and were quite cumbersome to run and debug locally. + +## New implementation for the Control Plane Adapter + +Since the old Control-Plane-Adapter is incompatible with DSP, a new iteration was created. +**Due to time constraints with this release documentation for this feature will to be published subsequently** diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 87dca3970..cc39bf725 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -23,25 +23,30 @@ plugins { } dependencies { + runtimeOnly(project(":core:edr-cache-core")) runtimeOnly(project(":edc-extensions:business-partner-validation")) runtimeOnly(project(":edc-extensions:dataplane-selector-configuration")) runtimeOnly(project(":edc-extensions:data-encryption")) runtimeOnly(project(":edc-extensions:cx-oauth2")) - runtimeOnly(project(":edc-extensions:control-plane-adapter")) runtimeOnly(project(":edc-extensions:provision-additional-headers")) runtimeOnly(project(":edc-extensions:observability-api-customization")) + runtimeOnly(project(":edc-extensions:control-plane-adapter-api")) + runtimeOnly(project(":edc-extensions:control-plane-adapter-callback")) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.config.filesystem) - runtimeOnly(edc.auth.tokenbased) - runtimeOnly(edc.auth.oauth2.core) - runtimeOnly(edc.auth.oauth2.daps) - runtimeOnly(edc.api.management) - runtimeOnly(edc.ids) - runtimeOnly(edc.spi.jwt) - runtimeOnly(edc.bundles.dpf) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.config.filesystem) + runtimeOnly(libs.edc.auth.tokenbased) + runtimeOnly(libs.edc.auth.oauth2.core) + runtimeOnly(libs.edc.auth.oauth2.daps) + runtimeOnly(libs.edc.api.management) + runtimeOnly(libs.edc.dsp) + runtimeOnly(libs.edc.spi.jwt) + runtimeOnly(libs.bundles.edc.dpf) + + runtimeOnly(libs.edc.ext.http) + runtimeOnly(libs.bundles.edc.monitoring) + runtimeOnly(libs.edc.transfer.dynamicreceiver) + runtimeOnly(libs.edc.controlplane.callback.dispatcher.event) + runtimeOnly(libs.edc.controlplane.callback.dispatcher.http) - runtimeOnly(edc.ext.http) - runtimeOnly(edc.bundles.monitoring) - runtimeOnly(edc.transfer.dynamicreceiver) } diff --git a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts index 0f69332a4..c6120480c 100644 --- a/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-memory-hashicorp-vault/build.gradle.kts @@ -27,8 +27,8 @@ plugins { dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:hashicorp-vault")) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.dpf.transfer) } diff --git a/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts index 3367ab2c8..90972bdef 100644 --- a/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-azure-vault/build.gradle.kts @@ -29,12 +29,13 @@ plugins { dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) - runtimeOnly(edc.azure.vault) - runtimeOnly(edc.bundles.sqlstores) - runtimeOnly(edc.transaction.local) - runtimeOnly(edc.sql.pool) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) + runtimeOnly(libs.edc.azure.vault) + runtimeOnly(libs.bundles.edc.sqlstores) + runtimeOnly(libs.edc.transaction.local) + runtimeOnly(libs.edc.sql.pool) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.dpf.transfer) + runtimeOnly(libs.postgres) } diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index e760bc0c8..79ce6ef7b 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -30,12 +30,12 @@ dependencies { runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(project(":edc-extensions:postgresql-migration")) runtimeOnly(project(":edc-extensions:hashicorp-vault")) - runtimeOnly(edc.bundles.sqlstores) - runtimeOnly(edc.transaction.local) - runtimeOnly(edc.sql.pool) - runtimeOnly(edc.core.controlplane) - runtimeOnly(edc.dpf.transfer) - + runtimeOnly(libs.bundles.edc.sqlstores) + runtimeOnly(libs.edc.transaction.local) + runtimeOnly(libs.edc.sql.pool) + runtimeOnly(libs.edc.core.controlplane) + runtimeOnly(libs.edc.dpf.transfer) + runtimeOnly(libs.postgres) } diff --git a/edc-controlplane/edc-runtime-memory/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts index b834ed12f..1df3d6915 100644 --- a/edc-controlplane/edc-runtime-memory/build.gradle.kts +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -24,12 +24,12 @@ plugins { } dependencies { - implementation(edc.spi.core) + implementation(libs.edc.spi.core) runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "data-encryption") } runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) - runtimeOnly(edc.core.controlplane) + runtimeOnly(libs.edc.core.controlplane) } tasks.withType { diff --git a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts index 1ae42a29a..134319535 100644 --- a/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-azure-vault/build.gradle.kts @@ -25,9 +25,14 @@ plugins { dependencies { implementation(project(":edc-dataplane:edc-dataplane-base")) - implementation(edc.azure.vault) - implementation(edc.azure.identity) - implementation("com.azure:azure-security-keyvault-secrets:4.6.1") + implementation(libs.edc.azure.vault) + constraints { + implementation("net.minidev:json-smart:2.4.10") { + because("version 2.4.8 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1370.") + } + } + implementation(libs.edc.azure.identity) + implementation("com.azure:azure-security-keyvault-secrets:4.6.0") } tasks.withType { diff --git a/edc-dataplane/edc-dataplane-base/build.gradle.kts b/edc-dataplane/edc-dataplane-base/build.gradle.kts index af49bf16d..09d88a015 100644 --- a/edc-dataplane/edc-dataplane-base/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-base/build.gradle.kts @@ -25,16 +25,16 @@ plugins { dependencies { runtimeOnly(project(":edc-extensions:observability-api-customization")) - runtimeOnly(edc.config.filesystem) - runtimeOnly(edc.dpf.awss3) - runtimeOnly(edc.dpf.oauth2) - runtimeOnly(edc.dpf.http) + runtimeOnly(libs.edc.config.filesystem) + runtimeOnly(libs.edc.dpf.awss3) + runtimeOnly(libs.edc.dpf.oauth2) + runtimeOnly(libs.edc.dpf.http) - runtimeOnly(edc.dpf.framework) - runtimeOnly(edc.dpf.api) - runtimeOnly(edc.core.connector) - runtimeOnly(edc.boot) + runtimeOnly(libs.edc.dpf.framework) + runtimeOnly(libs.edc.dpf.api) + runtimeOnly(libs.edc.core.connector) + runtimeOnly(libs.edc.boot) - runtimeOnly(edc.bundles.monitoring) - runtimeOnly(edc.ext.http) -} \ No newline at end of file + runtimeOnly(libs.bundles.edc.monitoring) + runtimeOnly(libs.edc.ext.http) +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts new file mode 100644 index 000000000..6af73c993 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + + implementation(libs.jakarta.rsApi) + + implementation(libs.edc.spi.http) + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.ext.http) + + implementation(project(":spi:edr-cache-spi")) + + testImplementation(libs.edc.junit) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java new file mode 100644 index 000000000..566a63b7b --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api; + +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.ConsumerAssetRequestController; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.ClientErrorExceptionMapper; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +import java.util.concurrent.ExecutorService; + +import static java.util.concurrent.Executors.newFixedThreadPool; + +/** + * Instantiates the Proxy Data API for the consumer-side data plane. + */ +@Extension(value = DataPlaneProxyConsumerApiExtension.NAME) +public class DataPlaneProxyConsumerApiExtension implements ServiceExtension { + static final String NAME = "Data Plane Proxy Consumer API"; + + private static final int DEFAULT_PROXY_PORT = 8186; + private static final String CONSUMER_API_ALIAS = "consumer.api"; + private static final String CONSUMER_CONTEXT_PATH = "/proxy"; + private static final String CONSUMER_CONFIG_KEY = "web.http.proxy"; + + @Setting(value = "Data plane proxy API consumer port", type = "int") + private static final String CONSUMER_PORT = "tx.dpf.consumer.proxy.port"; + + @Setting(value = "Thread pool size for the consumer data plane proxy gateway", type = "int") + private static final String THREAD_POOL_SIZE = "tx.dpf.consumer.proxy.thread.pool"; + + public static final int DEFAULT_THREAD_POOL = 10; + + @Inject + private WebService webService; + + @Inject + private WebServer webServer; + + @Inject + private DataPlaneManager dataPlaneManager; + + @Inject + private EndpointDataReferenceCache edrCache; + + @Inject + private WebServiceConfigurer configurer; + + @Inject + private Monitor monitor; + + private ExecutorService executorService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var port = context.getSetting(CONSUMER_PORT, DEFAULT_PROXY_PORT); + configurer.configure(context, webServer, createApiContext(port)); + + executorService = newFixedThreadPool(context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL)); + + webService.registerResource(CONSUMER_API_ALIAS, new ClientErrorExceptionMapper()); + webService.registerResource(CONSUMER_API_ALIAS, new ConsumerAssetRequestController(edrCache, dataPlaneManager, executorService, monitor)); + } + + @Override + public void shutdown() { + if (executorService != null) { + executorService.shutdown(); + } + } + + private WebServiceSettings createApiContext(int port) { + return WebServiceSettings.Builder.newInstance() + .apiConfigKey(CONSUMER_CONFIG_KEY) + .contextAlias(CONSUMER_API_ALIAS) + .defaultPath(CONSUMER_CONTEXT_PATH) + .defaultPort(port) + .name(NAME) + .build(); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java new file mode 100644 index 000000000..386428084 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ClientErrorExceptionMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.ClientErrorException; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +/** + * Maps client errors to return the associated status. + */ +@Provider +public class ClientErrorExceptionMapper implements ExceptionMapper { + + public ClientErrorExceptionMapper() { + } + + @Override + public Response toResponse(ClientErrorException exception) { + return Response.status(exception.getResponse().getStatus()).build(); + } +} + + diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java new file mode 100644 index 000000000..7902566aa --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestApi.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model.AssetRequest; + +/** + * Defines the API for receiving asset requests on a consumer. + */ +@OpenAPIDefinition +@Tag(name = "Data Plane Proxy API") +public interface ConsumerAssetRequestApi { + + @Operation(responses = { + @ApiResponse(content = @Content(mediaType = "application/json", schema = @Schema(implementation = AssetRequest.class)), description = "Requests asset data") + }) + void requestAsset(AssetRequest request, @Suspended AsyncResponse response); +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java new file mode 100644 index 000000000..2ceb4a393 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.StreamingOutput; +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model.AssetRequest; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; + +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.BAD_GATEWAY; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; +import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; +import static java.util.UUID.randomUUID; + +/** + * Implements the HTTP proxy API. + */ +@Path("/aas") +public class ConsumerAssetRequestController implements ConsumerAssetRequestApi { + private static final String HTTP_DATA = "HttpData"; + private static final String ASYNC_TYPE = "async"; + private static final String BASE_URL = "baseUrl"; + private static final String HEADER_AUTHORIZATION = "header:authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + private final EndpointDataReferenceCache edrCache; + private final DataPlaneManager dataPlaneManager; + private final Monitor monitor; + + private final ExecutorService executorService; + + public ConsumerAssetRequestController(EndpointDataReferenceCache edrCache, + DataPlaneManager dataPlaneManager, + ExecutorService executorService, + Monitor monitor) { + this.edrCache = edrCache; + this.dataPlaneManager = dataPlaneManager; + this.executorService = executorService; + this.monitor = monitor; + } + + @POST + @Path("/request") + @Override + public void requestAsset(AssetRequest request, @Suspended AsyncResponse response) { + // resolve the EDR and add it to the request + var edr = resolveEdr(request); + + var sourceAddress = DataAddress.Builder.newInstance() + .type(HTTP_DATA) + .property(BASE_URL, request.getEndpointUrl()) + .property(HEADER_AUTHORIZATION, BEARER_PREFIX + edr.getAuthCode()) + .build(); + + var destinationAddress = DataAddress.Builder.newInstance() + .type(ASYNC_TYPE) + .build(); + + var flowRequest = DataFlowRequest.Builder.newInstance() + .processId(randomUUID().toString()) + .trackable(false) + .sourceDataAddress(sourceAddress) + .destinationDataAddress(destinationAddress) + .traceContext(Map.of()) + .build(); + + // transfer the data asynchronously + var sink = new AsyncStreamingDataSink(consumer -> response.resume((StreamingOutput) consumer::accept), executorService, monitor); + + try { + dataPlaneManager.transfer(sink, flowRequest).whenComplete((result, throwable) -> handleCompletion(response, result, throwable)); + } catch (Exception e) { + reportError(response, e); + } + } + + private EndpointDataReference resolveEdr(AssetRequest request) { + if (request.getTransferProcessId() != null) { + var edr = edrCache.resolveReference(request.getTransferProcessId()); + if (edr == null) { + throw new BadRequestException("No EDR for transfer process: " + request.getTransferProcessId()); + } + return edr; + } else { + var resolvedEdrs = edrCache.referencesForAsset(request.getAssetId()); + if (resolvedEdrs.isEmpty()) { + throw new BadRequestException("No EDR for asset: " + request.getAssetId()); + } else if (resolvedEdrs.size() > 1) { + throw new PreconditionFailedException("More than one EDR for asset: " + request.getAssetId()); + } + return resolvedEdrs.get(0); + } + } + + /** + * Handles a request completion, checking for errors. If no errors are present, nothing needs to be done as the response will have already been written to the client. + */ + private void handleCompletion(AsyncResponse response, StreamResult result, Throwable throwable) { + if (result != null && result.failed()) { + switch (result.reason()) { + case NOT_FOUND: + response.resume(status(NOT_FOUND).type(APPLICATION_JSON).build()); + break; + case NOT_AUTHORIZED: + response.resume(status(UNAUTHORIZED).type(APPLICATION_JSON).build()); + break; + case GENERAL_ERROR: + response.resume(status(INTERNAL_SERVER_ERROR).type(APPLICATION_JSON).build()); + break; + } + } else if (throwable != null) { + reportError(response, throwable); + } + } + + /** + * Reports an error to the client. On the consumer side, the error is reported as a {@code BAD_GATEWAY} since the consumer data plane acts as proxy. + */ + private void reportError(AsyncResponse response, Throwable throwable) { + monitor.severe("Error processing gateway request", throwable); + var entity = status(BAD_GATEWAY).entity(format("'%s'", throwable.getMessage())).type(APPLICATION_JSON).build(); + response.resume(entity); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java new file mode 100644 index 000000000..24b865bf0 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/PreconditionFailedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset; + +import jakarta.ws.rs.ClientErrorException; + +import static jakarta.ws.rs.core.Response.Status.PRECONDITION_REQUIRED; + +/** + * Exception used to map a {@link jakarta.ws.rs.core.Response.Status#PRECONDITION_REQUIRED} response. + */ +public class PreconditionFailedException extends ClientErrorException { + + public PreconditionFailedException(String message) { + super(message, PRECONDITION_REQUIRED); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java new file mode 100644 index 000000000..79fbf70dd --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import static java.util.Objects.requireNonNull; + +/** + * A request for asset data. The request may contain a transfer process ID or asset ID and must specify an endpoint for retrieving the data. + */ +@JsonDeserialize(builder = AssetRequest.Builder.class) +@JsonTypeName("tx:assetrequest") +public class AssetRequest { + private String transferProcessId; + private String assetId; + private String endpointUrl; + + public String getTransferProcessId() { + return transferProcessId; + } + + public String getAssetId() { + return assetId; + } + + public String getEndpointUrl() { + return endpointUrl; + } + + private AssetRequest() { + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private final AssetRequest request; + + @JsonCreator + public static Builder newInstance() { + return new Builder(); + } + + public Builder transferProcessId(String transferProcessId) { + request.transferProcessId = transferProcessId; + return this; + } + + public Builder assetId(String assetId) { + request.assetId = assetId; + return this; + } + + public Builder endpointUrl(String endpointUrl) { + request.endpointUrl = endpointUrl; + return this; + } + + public AssetRequest build() { + if (request.assetId == null && request.transferProcessId == null) { + throw new NullPointerException("An assetId or endpointReferenceId must be set"); + } + requireNonNull(request.endpointUrl, "endpointUrl"); + return request; + } + + private Builder() { + request = new AssetRequest(); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..11229c1f5 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + + org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.DataPlaneProxyConsumerApiExtension diff --git a/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java new file mode 100644 index 000000000..5855b20dd --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-consumer-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/model/AssetRequestTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.consumer.api.asset.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class AssetRequestTest { + + @Test + void verify_SerializeDeserialize() throws JsonProcessingException { + var mapper = new ObjectMapper(); + + var request = AssetRequest.Builder.newInstance().assetId("asset1").endpointUrl("https://test.com").transferProcessId("tp1").build(); + var serialized = mapper.writeValueAsString(request); + + var deserialized = mapper.readValue(serialized, AssetRequest.class); + + assertThat(deserialized.getAssetId()).isEqualTo(request.getAssetId()); + assertThat(deserialized.getTransferProcessId()).isEqualTo(request.getTransferProcessId()); + assertThat(deserialized.getEndpointUrl()).isEqualTo(request.getEndpointUrl()); + } + + @Test + void verify_NullArguments() { + assertThatThrownBy(() -> AssetRequest.Builder.newInstance().endpointUrl("https://test.com").build()).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> AssetRequest.Builder.newInstance().assetId("asset1").build()).isInstanceOf(NullPointerException.class); + } + + @Test + void verify_AssetIdOrTransferProcessId() { + AssetRequest.Builder.newInstance().assetId("asset1").endpointUrl("https://test.com").build(); + AssetRequest.Builder.newInstance().transferProcessId("tp1").endpointUrl("https://test.com").build(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts new file mode 100644 index 000000000..37aabac01 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + + implementation(libs.edc.spi.http) + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.ext.http) + implementation(libs.edc.spi.jwt) + implementation(libs.edc.jwt.core) + + implementation(libs.jakarta.rsApi) + implementation(libs.nimbus.jwt) + + implementation(project(":edc-dataplane:edc-dataplane-proxy-provider-spi")) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java new file mode 100644 index 000000000..6e845090c --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/DataPlaneProxyProviderApiExtension.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api; + +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway.ProviderGatewayController; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; + +import java.util.concurrent.ExecutorService; + +import static java.util.concurrent.Executors.newFixedThreadPool; + +/** + * Adds the consumer proxy data plane API. + */ +@Extension(value = DataPlaneProxyProviderApiExtension.NAME) +public class DataPlaneProxyProviderApiExtension implements ServiceExtension { + static final String NAME = "Data Plane Proxy Provider API"; + + @Setting(value = "Thread pool size for the provider data plane proxy gateway", type = "int") + private static final String THREAD_POOL_SIZE = "tx.dpf.provider.proxy.thread.pool"; + + public static final int DEFAULT_THREAD_POOL = 10; + + @Inject + private WebService webService; + + @Inject + private DataPlaneManager dataPlaneManager; + + @Inject + private Monitor monitor; + + @Inject + private GatewayConfigurationRegistry configurationRegistry; + + @Inject + private AuthorizationHandlerRegistry authorizationRegistry; + + private ExecutorService executorService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + executorService = newFixedThreadPool(context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL)); + + var controller = new ProviderGatewayController(dataPlaneManager, + configurationRegistry, + authorizationRegistry, + executorService, + monitor); + + webService.registerResource(controller); + } + + + @Override + public void shutdown() { + if (executorService != null) { + executorService.shutdown(); + } + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java new file mode 100644 index 000000000..f951123b3 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayApi.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.Context; + +/** + * Open API definition. + */ +@OpenAPIDefinition +@Tag(name = "Data Plane Proxy API") +public interface ProviderGatewayApi { + + @Operation(responses = { + @ApiResponse(content = @Content(mediaType = "application/json"), description = "Gets asset data") + }) + void requestAsset(@Context ContainerRequestContext context, @Suspended AsyncResponse response); +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java new file mode 100644 index 000000000..2f392fdce --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/gateway/ProviderGatewayController.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.gateway; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.Suspended; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.PathSegment; +import jakarta.ws.rs.core.StreamingOutput; +import org.eclipse.edc.connector.dataplane.spi.manager.DataPlaneManager; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; +import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.transfer.DataFlowRequest; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; + +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; +import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; +import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.joining; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response.ResponseHelper.createMessageResponse; + +/** + * Implements the HTTP data proxy API. + */ +@Path("/" + ProviderGatewayController.GATEWAY_PATH) +public class ProviderGatewayController implements ProviderGatewayApi{ + protected static final String GATEWAY_PATH = "gateway"; + + private static final String HTTP_DATA = "HttpData"; + private static final String BASE_URL = "baseUrl"; + private static final String ASYNC = "async"; + + private static final int ALIAS_SEGMENT = 1; + private static final String BEARER_PREFIX = "Bearer "; + + private final DataPlaneManager dataPlaneManager; + private final GatewayConfigurationRegistry configurationRegistry; + private final AuthorizationHandlerRegistry authorizationRegistry; + + private final Monitor monitor; + + private final ExecutorService executorService; + + public ProviderGatewayController(DataPlaneManager dataPlaneManager, + GatewayConfigurationRegistry configurationRegistry, + AuthorizationHandlerRegistry authorizationRegistry, + ExecutorService executorService, + Monitor monitor) { + this.dataPlaneManager = dataPlaneManager; + this.configurationRegistry = configurationRegistry; + this.authorizationRegistry = authorizationRegistry; + this.executorService = executorService; + this.monitor = monitor; + } + + @GET + @Path("/{paths: .+}") + @Override + public void requestAsset(@Context ContainerRequestContext context, @Suspended AsyncResponse response) { + var tokens = context.getHeaders().get(HttpHeaders.AUTHORIZATION); + if (tokens == null || tokens.isEmpty()) { + response.resume(createMessageResponse(UNAUTHORIZED, "No bearer token", context.getMediaType())); + return; + } + var token = tokens.get(0); + if (!token.startsWith(BEARER_PREFIX)) { + response.resume(createMessageResponse(UNAUTHORIZED, "Invalid bearer token", context.getMediaType())); + return; + } else { + token = token.substring(BEARER_PREFIX.length()); + } + + var uriInfo = context.getUriInfo(); + var segments = uriInfo.getPathSegments(); + if (segments.size() < 3 || !GATEWAY_PATH.equals(segments.get(0).getPath())) { + response.resume(createMessageResponse(BAD_REQUEST, "Invalid path", context.getMediaType())); + return; + } + + var alias = segments.get(ALIAS_SEGMENT).getPath(); + var configuration = configurationRegistry.getConfiguration(alias); + if (configuration == null) { + response.resume(createMessageResponse(NOT_FOUND, "Unknown path", context.getMediaType())); + return; + } + + // calculate the sub-path, which all segments after the GATEWAY segment, including the alias segment + var subPath = segments.stream().skip(1).map(PathSegment::getPath).collect(joining("/")); + if (!authenticate(token, configuration.getAuthorizationType(), subPath, context, response)) { + return; + } + + // calculate the request path, which all segments after the alias segment + var requestPath = segments.stream().skip(2).map(PathSegment::getPath).collect(joining("/")); + var flowRequest = createRequest(requestPath, configuration); + + // transfer the data asynchronously + var sink = new AsyncStreamingDataSink(consumer -> response.resume((StreamingOutput) consumer::accept), executorService, monitor); + + try { + dataPlaneManager.transfer(sink, flowRequest).whenComplete((result, throwable) -> handleCompletion(response, result, throwable)); + } catch (Exception e) { + reportError(response, e); + } + } + + private DataFlowRequest createRequest(String subPath, GatewayConfiguration configuration) { + var path = configuration.getProxiedPath() + "/" + subPath; + + var sourceAddress = DataAddress.Builder.newInstance() + .type(HTTP_DATA) + .property(BASE_URL, path) + .build(); + + var destinationAddress = DataAddress.Builder.newInstance() + .type(ASYNC) + .build(); + + return DataFlowRequest.Builder.newInstance() + .processId(randomUUID().toString()) + .trackable(false) + .sourceDataAddress(sourceAddress) + .destinationDataAddress(destinationAddress) + .traceContext(Map.of()) + .build(); + } + + private boolean authenticate(String token, String authType, String subPath, ContainerRequestContext context, AsyncResponse response) { + var handler = authorizationRegistry.getHandler(authType); + if (handler == null) { + var correlationId = randomUUID().toString(); + monitor.severe(format("Authentication handler not configured for type: %s [id: %s]", authType, correlationId)); + response.resume(createMessageResponse(INTERNAL_SERVER_ERROR, format("Internal server error: %s", correlationId), context.getMediaType())); + return false; + } + + var authResponse = handler.authorize(token, subPath); + if (authResponse.failed()) { + response.resume(status(UNAUTHORIZED).build()); + return false; + } + return true; + } + + /** + * Handles a request completion, checking for errors. If no errors are present, nothing needs to be done as the response will have already been written to the client. + */ + private void handleCompletion(AsyncResponse response, StreamResult result, Throwable throwable) { + if (result != null && result.failed()) { + switch (result.reason()) { + case NOT_FOUND: + response.resume(status(NOT_FOUND).type(APPLICATION_JSON).build()); + break; + case NOT_AUTHORIZED: + response.resume(status(UNAUTHORIZED).type(APPLICATION_JSON).build()); + break; + case GENERAL_ERROR: + response.resume(status(INTERNAL_SERVER_ERROR).type(APPLICATION_JSON).build()); + break; + } + } else if (throwable != null) { + reportError(response, throwable); + } + } + + /** + * Reports an error to the client. On the provider side, the error is reported as a {@code INTERNAL_SERVER_ERROR} since the provider data plane is considered an origin server + * even though it may delegate requests to other internal sources. + */ + private void reportError(AsyncResponse response, Throwable throwable) { + monitor.severe("Error processing gateway request", throwable); + var entity = status(INTERNAL_SERVER_ERROR).entity(format("'%s'", throwable.getMessage())).type(APPLICATION_JSON).build(); + response.resume(entity); + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java new file mode 100644 index 000000000..816c7a446 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelper.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.jetbrains.annotations.Nullable; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN; +import static jakarta.ws.rs.core.Response.status; +import static java.lang.String.format; + +/** + * Utility functions for creating responses. + */ +public class ResponseHelper { + + /** + * Creates a response with a message encoded for the given media type. Currently, {@code APPLICATION_JSON} and {@code TEXT_PLAIN} are supported. + */ + public static Response createMessageResponse(Response.Status status, String message, @Nullable MediaType mediaType) { + if (mediaType != null && APPLICATION_JSON.equals(mediaType.toString())) { + return status(status).entity(format("'%s'", message)).type(APPLICATION_JSON).build(); + } else { + return status(status).entity(format("%s", message)).type(TEXT_PLAIN).build(); + } + } + + private ResponseHelper() { + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..be66cb9c7 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + +org.eclipse.tractusx.edc.dataplane.proxy.provider.api.DataPlaneProxyProviderApiExtension diff --git a/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java b/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java new file mode 100644 index 000000000..f9abd0327 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-api/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/api/response/ResponseHelperTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response; + +import org.junit.jupiter.api.Test; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; +import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.api.response.ResponseHelper.createMessageResponse; + +class ResponseHelperTest { + + @Test + void verify_responses() { + assertThat(createMessageResponse(INTERNAL_SERVER_ERROR, "Some error", APPLICATION_JSON_TYPE).getEntity()).isEqualTo("'Some error'"); + assertThat(createMessageResponse(INTERNAL_SERVER_ERROR, "Some error", TEXT_PLAIN_TYPE).getEntity()).isEqualTo("Some error"); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts new file mode 100644 index 000000000..fae93ad79 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + + implementation(libs.edc.util) + implementation(libs.edc.dpf.framework) + implementation(libs.edc.api.observability) + implementation(libs.edc.dpf.util) + implementation(libs.edc.jwt.core) + implementation(libs.edc.ext.http) + implementation(libs.edc.spi.http) + + implementation(libs.edc.spi.jwt) + + implementation(libs.jakarta.rsApi) + implementation(libs.nimbus.jwt) + + implementation(project(":edc-dataplane:edc-dataplane-proxy-provider-spi")) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java new file mode 100644 index 000000000..5f8dd66e2 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/ProxyProviderCoreExtension.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core; + +import com.nimbusds.jose.crypto.RSASSAVerifier; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.AuthorizationHandlerRegistryImpl; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.JwtAuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth.RsaPublicKeyParser; +import org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationRegistryImpl; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; +import org.jetbrains.annotations.NotNull; + +import static java.lang.String.format; +import static org.eclipse.edc.spi.result.Result.failure; +import static org.eclipse.edc.spi.result.Result.success; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.loadConfiguration; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; + +/** + * Registers default services for the data plane provider proxy implementation. + */ +@Extension(value = ProxyProviderCoreExtension.NAME) +@Provides({GatewayConfigurationRegistry.class, AuthorizationHandlerRegistry.class}) +public class ProxyProviderCoreExtension implements ServiceExtension { + static final String NAME = "Data Plane Provider Proxy Core"; + + @Setting + private static final String PUBLIC_KEY = "tx.dpf.data.proxy.public.key"; + + @Inject(required = false) + private AuthorizationExtension authorizationExtension; + + @Inject + private Vault vault; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var configurationRegistry = new GatewayConfigurationRegistryImpl(); + context.registerService(GatewayConfigurationRegistry.class, configurationRegistry); + + if (authorizationExtension == null) { + context.getMonitor().info("Proxy JWT authorization is configured to only validate tokens and not provide path access control"); + authorizationExtension = (c, p) -> success(); + } + + var authorizationRegistry = creatAuthorizationRegistry(); + context.registerService(AuthorizationHandlerRegistry.class, authorizationRegistry); + + loadConfiguration(context).forEach(configuration -> { + monitor.info(format("Registering gateway configuration alias `%s` to %s", configuration.getAlias(), configuration.getProxiedPath())); + configurationRegistry.register(configuration); + }); + } + + @NotNull + private AuthorizationHandlerRegistryImpl creatAuthorizationRegistry() { + var authorizationRegistry = new AuthorizationHandlerRegistryImpl(); + + authorizationRegistry.register(NO_AUTHORIZATION, (t, p) -> success()); + + authorizationRegistry.register(TOKEN_AUTHORIZATION, createJwtAuthorizationHandler()); + + return authorizationRegistry; + } + + @NotNull + private AuthorizationHandler createJwtAuthorizationHandler() { + var publicCertKey = vault.resolveSecret(PUBLIC_KEY); + + if (publicCertKey == null) { + monitor.warning("Data proxy public key not set in the vault. Disabling JWT authorization for the proxy data."); + return (t, p) -> failure("Authentication disabled"); + } + + var publicKey = new RsaPublicKeyParser().parsePublicKey(publicCertKey); + var verifier = new RSASSAVerifier(publicKey); + + return new JwtAuthorizationHandler(verifier, authorizationExtension, monitor); + } + + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java new file mode 100644 index 000000000..8c1878c73 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandlerRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation of the registry. + */ +public class AuthorizationHandlerRegistryImpl implements AuthorizationHandlerRegistry { + private final Map handlers = new HashMap<>(); + + @Override + public @Nullable AuthorizationHandler getHandler(String alias) { + return handlers.get(alias); + } + + @Override + public void register(String alias, AuthorizationHandler handler) { + handlers.put(alias, handler); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java new file mode 100644 index 000000000..a4d1ca315 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandler.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; + +import java.text.ParseException; + +import static org.eclipse.edc.spi.result.Result.failure; + +/** + * Authenticates JWTs using a provided verifier and delegates to an {@link AuthorizationExtension} to provide access control checks for the requested path. + */ +public class JwtAuthorizationHandler implements AuthorizationHandler { + private final JWSVerifier verifier; + private final AuthorizationExtension authorizationExtension; + private final Monitor monitor; + + public JwtAuthorizationHandler(JWSVerifier verifier, AuthorizationExtension authorizationExtension, Monitor monitor) { + this.verifier = verifier; + this.authorizationExtension = authorizationExtension; + this.monitor = monitor; + } + + @Override + public Result authorize(String token, String path) { + try { + var jwt = SignedJWT.parse(token); + var result = jwt.verify(verifier); + + if (!result) { + return failure("Invalid token"); + } + + var claimToken = ClaimToken.Builder.newInstance() + .claims(jwt.getJWTClaimsSet().getClaims()) + .build(); + + return authorizationExtension.authorize(claimToken, path); + } catch (ParseException | JOSEException e) { + monitor.info("Invalid JWT received: " + e.getMessage()); + return failure("Invalid token"); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java new file mode 100644 index 000000000..23d0a2af9 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParser.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.edc.spi.EdcException; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * A thread-safe parser than can read RSA public keys stored using PEM encoding. + */ +public class RsaPublicKeyParser { + private static final String HEADER = "-----BEGIN PUBLIC KEY-----"; + private static final String FOOTER = "-----END PUBLIC KEY-----"; + private final KeyFactory keyFactory; + + public RsaPublicKeyParser() { + try { + keyFactory = KeyFactory.getInstance("RSA"); + } catch (NoSuchAlgorithmException e) { + throw new EdcException(e); + } + } + + /** + * Parses the PEM-encoded key. + */ + public RSAPublicKey parsePublicKey(String serialized) { + var keyPortion = serialized.replace(HEADER, "").replace(FOOTER, "").replaceAll("\\s", ""); + + var publicKeyDer = Base64.getDecoder().decode(keyPortion); + var spec = new X509EncodedKeySpec(publicKeyDer); + try { + return (RSAPublicKey) keyFactory.generatePublic(spec); + } catch (InvalidKeySpecException e) { + throw new EdcException(e); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java new file mode 100644 index 000000000..430b1c38a --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoader.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; + +import java.util.List; + +import static java.util.stream.Collectors.toList; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.TOKEN_AUTHORIZATION; + +/** + * Loads gateway configuration from the {@link #TX_GATEWAY_PREFIX} prefix. + */ +public class GatewayConfigurationLoader { + static final String TX_GATEWAY_PREFIX = "tx.dpf.proxy.gateway"; + static final String AUTHORIZATION_TYPE = "authorization.type"; + static final String PROXIED_PATH = "proxied.path"; + + public static List loadConfiguration(ServiceExtensionContext context) { + var root = context.getConfig(TX_GATEWAY_PREFIX); + return root.partition().map(GatewayConfigurationLoader::createGatewayConfiguration).collect(toList()); + } + + private static GatewayConfiguration createGatewayConfiguration(Config config) { + return GatewayConfiguration.Builder.newInstance() + .alias(config.currentNode()) + .authorizationType(config.getString(AUTHORIZATION_TYPE, TOKEN_AUTHORIZATION)) + .proxiedPath(config.getString(PROXIED_PATH)) + .build(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java new file mode 100644 index 000000000..45a5b8d91 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfigurationRegistry; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation. + */ +public class GatewayConfigurationRegistryImpl implements GatewayConfigurationRegistry { + private final Map configurations = new HashMap<>(); + + @Override + public @Nullable GatewayConfiguration getConfiguration(String alias) { + return configurations.get(alias); + } + + @Override + public void register(GatewayConfiguration configuration) { + configurations.put(configuration.getAlias(), configuration); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..5153c83eb --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,13 @@ + # + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # + # Contributors: + # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + +org.eclipse.tractusx.edc.dataplane.proxy.provider.core.ProxyProviderCoreExtension diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java new file mode 100644 index 000000000..346481a55 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/AuthorizationHandlerRegistryImplTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationHandler; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class AuthorizationHandlerRegistryImplTest { + + @Test + void verify_registration() { + var registry = new AuthorizationHandlerRegistryImpl(); + registry.register("alias", mock(AuthorizationHandler.class)); + + assertThat(registry.getHandler("alias")).isNotNull(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java new file mode 100644 index 000000000..9b74fe54d --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/JwtAuthorizationHandlerTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization.AuthorizationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.spi.result.Result.failure; +import static org.eclipse.edc.spi.result.Result.success; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JwtAuthorizationHandlerTest { + private JwtAuthorizationHandler handler; + private AuthorizationExtension authExtension; + private JWSVerifier verifier; + + + @BeforeEach + void setUp() { + verifier = mock(JWSVerifier.class); + Monitor monitor = mock(Monitor.class); + authExtension = mock(AuthorizationExtension.class); + handler = new JwtAuthorizationHandler(verifier, authExtension, monitor); + } + + @Test + void verify_validCase() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(true); + when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(success()); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isTrue(); + } + + @Test + void verify_parseInValidToken() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(false); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isFalse(); + } + + @Test + void verify_notAuthorized() throws JOSEException { + when(verifier.verify(any(), any(), any())).thenReturn(true); + when(authExtension.authorize(isA(ClaimToken.class), eq("foo"))).thenReturn(failure("Not authorized")); + + var result = handler.authorize(TestTokens.TEST_TOKEN, "foo"); + + assertThat(result.succeeded()).isFalse(); + + verify(authExtension).authorize(isA(ClaimToken.class), eq("foo")); + } + + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java new file mode 100644 index 000000000..d345ce388 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/RsaPublicKeyParserTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Verifies RSA public key parsing. + */ +class RsaPublicKeyParserTest { + + @Test + void verify_canParseKey() { + var key = new RsaPublicKeyParser().parsePublicKey(TestTokens.generatePublic()); + assertNotNull(key); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java new file mode 100644 index 000000000..8db44bee8 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/auth/TestTokens.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.auth; + +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +/** + * Tokens for testing. + */ +public class TestTokens { + private static final String DELIMITER = "-----"; + private static final String HEADER = DELIMITER + "BEGIN" + " PUBLIC " + "KEY" + DELIMITER + "\n"; + private static final String FOOTER = "\n" + DELIMITER + "END" + " PUBLIC " + "KEY" + DELIMITER + "\n"; + + public static final String TEST_TOKEN = "eyJhbGciOiJSUzI1NiIsInZlcnNpb24iOnRydWV9.eyJpc3MiOiJ0ZXN0LWNvbm5lY3RvciIsInN1YiI6ImNvbnN1bWVyLWNvbm5lY3RvciIsImF1ZCI6InRlc3QtY29ubmVjdG9yIiwiaWF0IjoxNjgxOTEzNjM2LCJleHAiOjMzNDU5NzQwNzg4LCJjaWQiOiIzMmE2M2E3ZC04MGQ2LTRmMmUtOTBlNi04MGJhZjVmYzJiM2MifQ.QAuotoRxpEqfuzkTcTq2w5Tcyy3Rc3UzUjjvNc_zwgNROGLe-wO9tFET1dJ_I5BttRxkngDS37dS4R6lN5YXaGHgcH2rf_FuVcJUSFqTp_usGAcx6m7pQQwqpNdcYgmq0NJp3xP87EFPHAy4kBxB5bqpmx4J-zrj9U_gerZ2WlRqpu0SdgP0S5v5D1Gm-vYkLqgvsugrAWH3Ti7OjC5UMdj0kDFwro2NpMY8SSNryiVvBEv8hn0KZdhhebIqPdhqbEQZ9d8WKzcgoqQ3DBd4ijzkd3Fz7ADD2gy_Hxn8Hi2LcItuB514TjCxYAncTNqZC_JSFEyuxwcGFVz3LdSXgw"; + + + public static String generatePublic() { + try { + var generator = KeyPairGenerator.getInstance("RSA"); + var pair = generator.generateKeyPair(); + var encoded = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); + return HEADER + encoded + FOOTER; + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + } + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java new file mode 100644 index 000000000..b9eec317f --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationLoaderTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.AUTHORIZATION_TYPE; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.PROXIED_PATH; +import static org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration.GatewayConfigurationLoader.TX_GATEWAY_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration.NO_AUTHORIZATION; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GatewayConfigurationLoaderTest { + + @Test + void verify_loadConfiguration() { + var context = mock(ServiceExtensionContext.class); + + var config = ConfigFactory.fromMap( + Map.of(format("alias.%s", AUTHORIZATION_TYPE), NO_AUTHORIZATION, + format("alias.%s", PROXIED_PATH), "https://test.com")); + when(context.getConfig(TX_GATEWAY_PREFIX)).thenReturn(config); + + var configurations = GatewayConfigurationLoader.loadConfiguration(context); + + assertThat(configurations).isNotEmpty(); + var configuration = configurations.get(0); + + assertThat(configuration.getAlias()).isEqualTo("alias"); + assertThat(configuration.getAuthorizationType()).isEqualTo(NO_AUTHORIZATION); + assertThat(configuration.getProxiedPath()).isEqualTo("https://test.com"); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java new file mode 100644 index 000000000..9bd58c365 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-core/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/provider/core/gateway/configuration/GatewayConfigurationRegistryImplTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.provider.core.gateway.configuration; + +import org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration.GatewayConfiguration; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class GatewayConfigurationRegistryImplTest { + + @Test + void verify_Configuration() { + var registry = new GatewayConfigurationRegistryImpl(); + registry.register(GatewayConfiguration.Builder.newInstance().proxiedPath("https://test.com").alias("alias").build()); + + assertThat(registry.getConfiguration("alias")).isNotNull(); + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts b/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts new file mode 100644 index 000000000..9ca2f9437 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) +} + diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java new file mode 100644 index 000000000..37ffc5490 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationExtension.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.result.Result; + +/** + * Performs an authorization check for the given path against a set of claims. + */ +public interface AuthorizationExtension { + + /** + * Performs an authorization check for the given path against the presented claims. The path is the request alias path, not + * the proxied path. + * + * @param token the validated claim token + * @param path the request alias path, not the dereferenced proxied path + */ + Result authorize(ClaimToken token, String path); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java new file mode 100644 index 000000000..5442ebc98 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.eclipse.edc.spi.result.Result; + +/** + * Performs an authorization using the request token for a given path. Implementation support different token formats such as JWT. + */ +@FunctionalInterface +public interface AuthorizationHandler { + + /** + * Performs the authorization check. + * + * @param token the unvalidated token + * @param path the request alias path, not the dereferenced proxied path + */ + Result authorize(String token, String path); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java new file mode 100644 index 000000000..b40217cb4 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/authorization/AuthorizationHandlerRegistry.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.authorization; + +import org.jetbrains.annotations.Nullable; + +/** + * Manages {@link AuthorizationHandler}s. + */ +public interface AuthorizationHandlerRegistry { + + /** + * Returns a handler for the alias or null if not found. + */ + @Nullable + AuthorizationHandler getHandler(String alias); + + /** + * Registers a handler for the given alias. + */ + void register(String alias, AuthorizationHandler handler); + +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java new file mode 100644 index 000000000..baafbf4b6 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfiguration.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration; + +import static java.util.Objects.requireNonNull; + +/** + * A configuration that exposes a proxied endpoint via an alias. Each configuration is associated with an extensible {@code authorizationType} such as + * {@link #TOKEN_AUTHORIZATION} (the default) and {@link #NO_AUTHORIZATION}. The {@code proxiedPath} will be prepended to a request sub-path to create an absolute endpoint + * URL where data is fetched from. + */ +public class GatewayConfiguration { + public static final String TOKEN_AUTHORIZATION = "token"; + public static final String NO_AUTHORIZATION = "none"; + + private String alias; + private String proxiedPath; + private String authorizationType = TOKEN_AUTHORIZATION; + + public String getAlias() { + return alias; + } + + public String getProxiedPath() { + return proxiedPath; + } + + public String getAuthorizationType() { + return authorizationType; + } + + private GatewayConfiguration() { + } + + public static class Builder { + + private final GatewayConfiguration configuration; + + public static Builder newInstance() { + return new Builder(); + } + + public Builder alias(String alias) { + this.configuration.alias = alias; + return this; + } + + public Builder proxiedPath(String proxiedPath) { + this.configuration.proxiedPath = proxiedPath; + return this; + } + + public Builder authorizationType(String authorizationType) { + this.configuration.authorizationType = authorizationType; + return this; + } + + public GatewayConfiguration build() { + requireNonNull(configuration.alias, "alias"); + requireNonNull(configuration.proxiedPath, "proxiedPath"); + requireNonNull(configuration.authorizationType, "authorizationType"); + return configuration; + } + + private Builder() { + configuration = new GatewayConfiguration(); + } + } +} diff --git a/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java new file mode 100644 index 000000000..d96ddf730 --- /dev/null +++ b/edc-dataplane/edc-dataplane-proxy-provider-spi/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/spi/provider/gateway/configuration/GatewayConfigurationRegistry.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.spi.provider.gateway.configuration; + +import org.jetbrains.annotations.Nullable; + +/** + * Manages {@link GatewayConfiguration}s. + */ +public interface GatewayConfigurationRegistry { + + /** + * Returns the configuration for the given alias or null if not found. + */ + @Nullable + GatewayConfiguration getConfiguration(String alias); + + /** + * Registers a configuration for the given alias. + */ + void register(GatewayConfiguration configuration); + +} diff --git a/edc-extensions/build.gradle.kts b/edc-extensions/build.gradle.kts index 646417ebf..f047330fd 100644 --- a/edc-extensions/build.gradle.kts +++ b/edc-extensions/build.gradle.kts @@ -23,7 +23,6 @@ plugins { dependencies { implementation(project(":edc-extensions:business-partner-validation")) - implementation(project(":edc-extensions:control-plane-adapter")) implementation(project(":edc-extensions:cx-oauth2")) implementation(project(":edc-extensions:data-encryption")) implementation(project(":edc-extensions:dataplane-selector-configuration")) diff --git a/edc-extensions/business-partner-validation/build.gradle.kts b/edc-extensions/business-partner-validation/build.gradle.kts index 87311b589..b4996ba37 100644 --- a/edc-extensions/business-partner-validation/build.gradle.kts +++ b/edc-extensions/business-partner-validation/build.gradle.kts @@ -23,8 +23,8 @@ plugins { } dependencies { - api(edc.spi.core) - implementation(edc.spi.policy) - implementation(edc.spi.contract) - implementation(edc.spi.policyengine) + api(libs.edc.spi.core) + implementation(libs.edc.spi.policy) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.policyengine) } diff --git a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java index d88293a72..1786897bd 100644 --- a/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java +++ b/edc-extensions/business-partner-validation/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/BusinessPartnerValidationExtension.java @@ -35,6 +35,7 @@ import org.eclipse.tractusx.edc.validation.businesspartner.functions.BusinessPartnerProhibitionFunction; import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; public class BusinessPartnerValidationExtension implements ServiceExtension { @@ -54,7 +55,8 @@ public class BusinessPartnerValidationExtension implements ServiceExtension { * } * */ - public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = "BusinessPartnerNumber"; + // TODO replace with TX namespace + public static final String BUSINESS_PARTNER_CONSTRAINT_KEY = EDC_NAMESPACE + "BusinessPartnerNumber"; public static final String DEFAULT_LOG_AGREEMENT_EVALUATION = "true"; diff --git a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java index 2bc0738b0..c6ed58e43 100644 --- a/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java +++ b/edc-extensions/business-partner-validation/src/test/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/AbstractBusinessPartnerValidationTest.java @@ -159,8 +159,8 @@ void testValidationWhenSingleParticipantIsValidWithAgreement() { var agreement = ContractAgreement.Builder.newInstance() .id("agreementId") - .providerAgentId("provider") - .consumerAgentId("consumer") + .providerId("provider") + .consumerId("consumer") .assetId("assetId") .policy(Policy.Builder.newInstance().build()) .build(); diff --git a/edc-extensions/control-plane-adapter-api/build.gradle.kts b/edc-extensions/control-plane-adapter-api/build.gradle.kts new file mode 100644 index 000000000..2541d1346 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + `maven-publish` + id("io.swagger.core.v3.swagger-gradle-plugin") +} + +dependencies { + implementation(project(":spi:control-plane-adapter-spi")) + implementation(libs.edc.api.management) + implementation(libs.edc.spi.aggregateservices) + implementation(libs.jakarta.rsApi) + + testImplementation(testFixtures(libs.edc.core.jersey)) + testImplementation(libs.restAssured) + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java new file mode 100644 index 000000000..e67f35579 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterApiExtension.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.JsonObjectToNegotiateEdrRequestDtoTransformer; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +public class AdapterApiExtension implements ServiceExtension { + + @Inject + private WebService webService; + @Inject + private ManagementApiConfiguration apiConfig; + + @Inject + private AdapterTransferProcessService adapterTransferProcessService; + + @Inject + private TypeTransformerRegistry transformerRegistry; + + @Inject + private JsonLd jsonLdService; + + @Override + public void initialize(ServiceExtensionContext context) { + transformerRegistry.register(new NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer()); + transformerRegistry.register(new JsonObjectToNegotiateEdrRequestDtoTransformer()); + webService.registerResource(apiConfig.getContextAlias(), new AdapterEdrController(adapterTransferProcessService, jsonLdService, transformerRegistry)); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java new file mode 100644 index 000000000..d10d133ba --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApi.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.json.JsonObject; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.web.spi.ApiErrorDetail; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; + +@OpenAPIDefinition +@Tag(name = "Control Plane Adapter EDR Api") +public interface AdapterEdrApi { + + @Operation(description = "Initiates an EDR negotiation by handling a contract negotiation first and then a transfer process for a given offer and with the given counter part. Please note that successfully invoking this endpoint " + + "only means that the negotiation was initiated.", + responses = { + @ApiResponse(responseCode = "200", description = "The negotiation was successfully initiated.", + content = @Content(schema = @Schema(implementation = IdResponseDto.class))), + @ApiResponse(responseCode = "400", description = "Request body was malformed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + }) + JsonObject initiateEdrNegotiation(@Schema(implementation = NegotiateEdrRequestDto.class) JsonObject dto); +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java new file mode 100644 index 000000000..bfbbc483f --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrController.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.exception.InvalidRequestException; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; + +@Consumes({MediaType.APPLICATION_JSON}) +@Produces({MediaType.APPLICATION_JSON}) +@Path("/adapter/edrs") +public class AdapterEdrController implements AdapterEdrApi { + + private final AdapterTransferProcessService adapterTransferProcessService; + private final TypeTransformerRegistry transformerRegistry; + private final JsonLd jsonLdService; + + public AdapterEdrController(AdapterTransferProcessService adapterTransferProcessService, JsonLd jsonLdService, TypeTransformerRegistry transformerRegistry) { + this.adapterTransferProcessService = adapterTransferProcessService; + this.jsonLdService = jsonLdService; + this.transformerRegistry = transformerRegistry; + } + + @POST + @Override + public JsonObject initiateEdrNegotiation(JsonObject requestObject) { + var edrNegotiationRequest = jsonLdService.expand(requestObject) + .compose(expanded -> transformerRegistry.transform(expanded, NegotiateEdrRequestDto.class)) + .compose(dto -> transformerRegistry.transform(dto, NegotiateEdrRequest.class)) + .orElseThrow(InvalidRequestException::new); + + var contractNegotiation = adapterTransferProcessService.initiateEdrNegotiation(edrNegotiationRequest).orElseThrow(exceptionMapper(NegotiateEdrRequest.class)); + + var responseDto = IdResponseDto.Builder.newInstance() + .id(contractNegotiation.getId()) + .createdAt(contractNegotiation.getCreatedAt()) + .build(); + + return transformerRegistry.transform(responseDto, JsonObject.class) + .compose(jsonLdService::compact) + .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java new file mode 100644 index 000000000..074c7017e --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDto.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; + +import java.util.ArrayList; +import java.util.List; + +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class NegotiateEdrRequestDto { + + public static final String TYPE = EDC_NAMESPACE + "NegotiateEdrRequestDto"; + public static final String CONNECTOR_ADDRESS = EDC_NAMESPACE + "connectorAddress"; + public static final String PROTOCOL = EDC_NAMESPACE + "protocol"; + public static final String CONNECTOR_ID = EDC_NAMESPACE + "connectorId"; + public static final String PROVIDER_ID = EDC_NAMESPACE + "providerId"; + public static final String OFFER = EDC_NAMESPACE + "offer"; + public static final String CALLBACK_ADDRESSES = EDC_NAMESPACE + "callbackAddresses"; + + @NotBlank(message = "connectorAddress is mandatory") + private String connectorAddress; + @NotBlank(message = "protocol is mandatory") + private String protocol = "ids-multipart"; + @NotBlank(message = "connectorId is mandatory") + private String connectorId; + + private String providerId; + + @NotNull(message = "offer cannot be null") + private ContractOfferDescription offer; + private List callbackAddresses = new ArrayList<>(); + + private NegotiateEdrRequestDto() { + + } + + public String getConnectorAddress() { + return connectorAddress; + } + + public String getProtocol() { + return protocol; + } + + public String getConnectorId() { + return connectorId; + } + + public String getProviderId() { + return providerId; + } + + public List getCallbackAddresses() { + return callbackAddresses; + } + + public ContractOfferDescription getOffer() { + return offer; + } + + public static final class Builder { + private final NegotiateEdrRequestDto dto; + + private Builder() { + dto = new NegotiateEdrRequestDto(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder connectorAddress(String connectorAddress) { + dto.connectorAddress = connectorAddress; + return this; + } + + public Builder protocol(String protocol) { + dto.protocol = protocol; + return this; + } + + public Builder connectorId(String connectorId) { + dto.connectorId = connectorId; + return this; + } + + public Builder offer(ContractOfferDescription offer) { + dto.offer = offer; + return this; + } + + public Builder providerId(String providerId) { + dto.providerId = providerId; + return this; + } + + public Builder callbackAddresses(List callbackAddresses) { + dto.callbackAddresses = callbackAddresses; + return this; + } + + public NegotiateEdrRequestDto build() { + return dto; + } + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java new file mode 100644 index 000000000..02a53ab18 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformer.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CALLBACK_ADDRESSES; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CONNECTOR_ADDRESS; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.CONNECTOR_ID; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.OFFER; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.PROTOCOL; +import static org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto.PROVIDER_ID; + + +public class JsonObjectToNegotiateEdrRequestDtoTransformer extends AbstractJsonLdTransformer { + + public JsonObjectToNegotiateEdrRequestDtoTransformer() { + super(JsonObject.class, NegotiateEdrRequestDto.class); + } + + @Override + public @Nullable NegotiateEdrRequestDto transform(@NotNull JsonObject jsonObject, @NotNull TransformerContext context) { + var builder = NegotiateEdrRequestDto.Builder.newInstance(); + + visitProperties(jsonObject, (k, v) -> setProperties(k, v, builder, context)); + return builder.build(); + } + + private void setProperties(String key, JsonValue value, NegotiateEdrRequestDto.Builder builder, TransformerContext context) { + switch (key) { + case CONNECTOR_ADDRESS: + transformString(value, builder::connectorAddress, context); + break; + case PROTOCOL: + transformString(value, builder::protocol, context); + break; + case CONNECTOR_ID: + transformString(value, builder::connectorId, context); + break; + case PROVIDER_ID: + transformString(value, builder::providerId, context); + break; + case CALLBACK_ADDRESSES: + var addresses = new ArrayList(); + transformArrayOrObject(value, CallbackAddressDto.class, addresses::add, context); + builder.callbackAddresses(addresses); + break; + case OFFER: + transformArrayOrObject(value, ContractOfferDescription.class, builder::offer, context); + break; + default: + context.problem() + .unexpectedType() + .type(NegotiateEdrRequestDto.TYPE) + .property(key) + .actual(key) + .expected(CONNECTOR_ADDRESS) + .expected(PROTOCOL) + .expected(CONNECTOR_ID) + .expected(PROVIDER_ID) + .expected(CALLBACK_ADDRESSES) + .expected(OFFER) + .report(); + break; + } + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java new file mode 100644 index 000000000..25878c0ed --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import org.eclipse.edc.api.transformer.DtoTransformer; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Collectors; + +public class NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer implements DtoTransformer { + + @Override + public Class getInputType() { + return NegotiateEdrRequestDto.class; + } + + @Override + public Class getOutputType() { + return NegotiateEdrRequest.class; + } + + @Override + public @Nullable NegotiateEdrRequest transform(@NotNull NegotiateEdrRequestDto object, @NotNull TransformerContext context) { + var callbacks = object.getCallbackAddresses().stream().map(c -> context.transform(c, CallbackAddress.class)).collect(Collectors.toList()); + + var contractOffer = ContractOffer.Builder.newInstance() + .id(object.getOffer().getOfferId()) + .assetId(object.getOffer().getAssetId()) + .providerId(getId(object.getProviderId(), object.getConnectorAddress())) + .policy(object.getOffer().getPolicy()) + .build(); + + return NegotiateEdrRequest.Builder.newInstance() + .connectorId(object.getConnectorId()) + .connectorAddress(object.getConnectorAddress()) + .protocol(object.getProtocol()) + .offer(contractOffer) + .callbackAddresses(callbacks) + .build(); + } + + private String getId(String value, String defaultValue) { + return value != null ? value : defaultValue; + } + +} diff --git a/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..23ba7b21c --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.api.cp.adapter.AdapterApiExtension diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java new file mode 100644 index 000000000..fbe6c1744 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrApiExtensionTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.JsonObjectToNegotiateEdrRequestDtoTransformer; +import org.eclipse.tractusx.edc.api.cp.adapter.transform.NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +public class AdapterEdrApiExtensionTest { + + AdapterApiExtension extension; + + TypeTransformerRegistry transformerRegistry = mock(TypeTransformerRegistry.class); + + WebService webService = mock(WebService.class); + + ManagementApiConfiguration configuration = mock(ManagementApiConfiguration.class); + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + context.registerService(WebService.class, webService); + context.registerService(TypeTransformerRegistry.class, transformerRegistry); + context.registerService(ManagementApiConfiguration.class, configuration); + extension = factory.constructInstance(AdapterApiExtension.class); + } + + @Test + void initialize_ShouldConfigureTheController(ServiceExtensionContext context) { + var alias = "context"; + + when(configuration.getContextAlias()).thenReturn(alias); + extension.initialize(context); + + verify(webService).registerResource(eq(alias), isA(AdapterEdrController.class)); + verify(transformerRegistry).register(isA(NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer.class)); + verify(transformerRegistry).register(isA(JsonObjectToNegotiateEdrRequestDtoTransformer.class)); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java new file mode 100644 index 000000000..022aa60df --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import io.restassured.specification.RequestSpecification; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.api.model.IdResponseDto; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.eclipse.edc.api.model.IdResponseDto.EDC_ID_RESPONSE_DTO_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.openRequest; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.requestDto; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ApiTest +public class AdapterEdrControllerTest extends RestControllerTestBase { + + public static final String ADAPTER_EDR_PATH = "/adapter/edrs"; + private final JsonLd jsonLdService = new TitaniumJsonLd(monitor); + AdapterTransferProcessService adapterTransferProcessService = mock(AdapterTransferProcessService.class); + TypeTransformerRegistry transformerRegistry = mock(TypeTransformerRegistry.class); + + @Test + void initEdrNegotiation_shouldWork_whenValidRequest() { + + var openRequest = openRequest(); + var contractNegotiation = getContractNegotiation(); + var responseBody = Json.createObjectBuilder().add(TYPE, EDC_ID_RESPONSE_DTO_TYPE).add(ID, contractNegotiation.getId()).build(); + + when(transformerRegistry.transform(any(JsonObject.class), eq(NegotiateEdrRequestDto.class))).thenReturn(Result.success(NegotiateEdrRequestDto.Builder.newInstance().build())); + when(transformerRegistry.transform(any(), eq(NegotiateEdrRequest.class))).thenReturn(Result.success(openRequest)); + when(adapterTransferProcessService.initiateEdrNegotiation(openRequest)).thenReturn(ServiceResult.success(contractNegotiation)); + when(transformerRegistry.transform(any(IdResponseDto.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); + var request = requestDto(); + + baseRequest() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .post(ADAPTER_EDR_PATH) + .then() + .statusCode(200) + .body(ID, is(contractNegotiation.getId())); + + } + + @Test + void initEdrNegotiation_shouldReturnBadRequest_whenValidInvalidRequest() { + + var request = NegotiateEdrRequestDto.Builder.newInstance().build(); + when(transformerRegistry.transform(any(JsonObject.class), eq(NegotiateEdrRequestDto.class))).thenReturn(Result.failure("fail")); + + baseRequest() + .contentType(MediaType.APPLICATION_JSON) + .body(request) + .post(ADAPTER_EDR_PATH) + .then() + .statusCode(400); + + } + + @Override + protected Object controller() { + return new AdapterEdrController(adapterTransferProcessService, jsonLdService, transformerRegistry); + } + + private RequestSpecification baseRequest() { + return given() + .baseUri("http://localhost:" + port) + .basePath("/") + .when(); + } + + private ContractNegotiation getContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .id("id") + .counterPartyAddress("http://test") + .counterPartyId("provider") + .protocol("protocol") + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java new file mode 100644 index 000000000..ec1a89824 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter; + +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; + +import java.util.UUID; + +public class TestFunctions { + + public static ContractOfferDescription createOffer(String offerId, String assetId) { + return ContractOfferDescription.Builder.newInstance() + .offerId(offerId) + .assetId(assetId) + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + public static ContractOfferDescription createOffer(Policy policy) { + return ContractOfferDescription.Builder.newInstance() + .offerId(UUID.randomUUID().toString()) + .assetId(UUID.randomUUID().toString()) + .policy(policy) + .build(); + } + + public static ContractOfferDescription createOffer(String offerId) { + return createOffer(offerId, UUID.randomUUID().toString()); + } + + public static ContractOfferDescription createOffer() { + return createOffer(UUID.randomUUID().toString(), UUID.randomUUID().toString()); + } + + public static NegotiateEdrRequestDto requestDto() { + return NegotiateEdrRequestDto.Builder.newInstance() + .connectorAddress("test") + .connectorId("id") + .protocol("test-protocol") + .offer(ContractOfferDescription.Builder.newInstance() + .offerId("offerId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()).build()) + .build(); + } + + public static NegotiateEdrRequest openRequest() { + return NegotiateEdrRequest.Builder.newInstance() + .connectorAddress("test") + .connectorId("id") + .protocol("test-protocol") + .offer(ContractOffer.Builder.newInstance() + .id("offerId") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()).build()) + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java new file mode 100644 index 000000000..88ee7633a --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/dto/NegotiateEdrRequestDtoValidationTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.dto; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NegotiateEdrRequestDtoValidationTest { + + private Validator validator; + + @BeforeEach + void setUp() { + try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) { + validator = factory.getValidator(); + } + } + + @Test + void validate_invalidDto() { + var dto = NegotiateEdrRequestDto.Builder.newInstance().build(); + assertThat(validator.validate(dto)).hasSize(3); + } + +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java new file mode 100644 index 000000000..5c52ad494 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/JsonObjectToNegotiateEdrRequestDtoTransformerTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; +import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.transform.spi.ProblemBuilder; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.ASSET_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CALLBACK_ADDRESSES; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CONNECTOR_ADDRESS; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.CONNECTOR_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.OFFER; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.OFFER_ID; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.POLICY; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.PROTOCOL; +import static org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto.PROVIDER_ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OBLIGATION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_SET; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PROHIBITION_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.EVENTS; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.IS_TRANSACTIONAL; +import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.URI; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JsonObjectToNegotiateEdrRequestDtoTransformerTest { + + private final JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + private final TransformerContext context = mock(TransformerContext.class); + private JsonObjectToNegotiateEdrRequestDtoTransformer transformer; + + @BeforeEach + void setUp() { + transformer = new JsonObjectToNegotiateEdrRequestDtoTransformer(); + } + + @Test + void transform() { + var jsonObject = Json.createObjectBuilder() + .add(TYPE, NegotiationInitiateRequestDto.TYPE) + .add(CONNECTOR_ADDRESS, "test-address") + .add(PROTOCOL, "test-protocol") + .add(CONNECTOR_ID, "test-conn-id") + .add(PROVIDER_ID, "test-provider-id") + .add(CALLBACK_ADDRESSES, createCallbackAddress()) + .add(OFFER, Json.createObjectBuilder() + .add(OFFER_ID, "test-offer-id") + .add(ASSET_ID, "test-asset") + .add(POLICY, createPolicy()) + .build()) + .build(); + + when(context.transform(any(JsonValue.class), eq(ContractOfferDescription.class))).thenReturn(ContractOfferDescription.Builder.newInstance().build()); + + when(context.transform(any(JsonObject.class), eq(CallbackAddress.class))).thenReturn(CallbackAddress.Builder.newInstance() + .uri("http://test.local") + .events(Set.of("foo", "bar")) + .transactional(true) + .build()); + when(context.transform(any(CallbackAddress.class), eq(CallbackAddressDto.class))).thenReturn(CallbackAddressDto.Builder.newInstance() + .uri("http://test.local") + .events(Set.of("foo", "bar")) + .transactional(true) + .build()); + var dto = transformer.transform(jsonLd.expand(jsonObject).getContent(), context); + + assertThat(dto).isNotNull(); + assertThat(dto.getCallbackAddresses()).isNotEmpty(); + assertThat(dto.getProtocol()).isEqualTo("test-protocol"); + assertThat(dto.getConnectorAddress()).isEqualTo("test-address"); + assertThat(dto.getConnectorId()).isEqualTo("test-conn-id"); + assertThat(dto.getProviderId()).isEqualTo("test-provider-id"); + assertThat(dto.getOffer()).isNotNull(); + + } + + @Test + void transform_reportErrors() { + + when(context.problem()).thenReturn(new ProblemBuilder(context)); + + var jsonObject = Json.createObjectBuilder() + .add(TYPE, NegotiationInitiateRequestDto.TYPE) + .add(EDC_NAMESPACE + "notFound", "test-address") + .build(); + + var dto = transformer.transform(jsonLd.expand(jsonObject).getContent(), context); + + assertThat(dto).isNotNull(); + verify(context, times(1)).reportProblem(anyString()); + } + + private JsonArrayBuilder createCallbackAddress() { + var builder = Json.createArrayBuilder(); + return builder.add(Json.createObjectBuilder() + .add(IS_TRANSACTIONAL, true) + .add(URI, "http://test.local/") + .add(EVENTS, Json.createArrayBuilder().build())); + } + + private JsonObject createPolicy() { + var permissionJson = getJsonObject("permission"); + var prohibitionJson = getJsonObject("prohibition"); + var dutyJson = getJsonObject("duty"); + return Json.createObjectBuilder() + .add(TYPE, ODRL_POLICY_TYPE_SET) + .add(ODRL_PERMISSION_ATTRIBUTE, permissionJson) + .add(ODRL_PROHIBITION_ATTRIBUTE, prohibitionJson) + .add(ODRL_OBLIGATION_ATTRIBUTE, dutyJson) + .build(); + } + + private JsonObject getJsonObject(String type) { + return Json.createObjectBuilder() + .add(TYPE, type) + .build(); + } +} \ No newline at end of file diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java new file mode 100644 index 000000000..f51ba1435 --- /dev/null +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/transform/NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.api.cp.adapter.transform; + +import org.eclipse.edc.api.model.CallbackAddressDto; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.eclipse.tractusx.edc.api.cp.adapter.dto.NegotiateEdrRequestDto; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.createOffer; +import static org.mockito.Mockito.mock; + +public class NegotiateEdrRequestDtoToNegotiateEdrRequestTransformerTest { + + private final NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer transformer = new NegotiateEdrRequestDtoToNegotiatedEdrRequestTransformer(); + + private final TransformerContext context = mock(TransformerContext.class); + + @Test + void inputOutputType() { + assertThat(transformer.getInputType()).isNotNull(); + assertThat(transformer.getOutputType()).isNotNull(); + } + + @Test + void verify_transform() { + var callback = CallbackAddressDto.Builder.newInstance() + .uri("local://test") + .build(); + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + .providerId("test-provider") + .offer(createOffer("offerId", "assetId")) + .callbackAddresses(List.of(callback)) + .build(); + + var request = transformer.transform(dto, context); + + assertThat(request).isNotNull(); + assertThat(request.getConnectorId()).isEqualTo("connectorId"); + assertThat(request.getConnectorAddress()).isEqualTo("address"); + assertThat(request.getProtocol()).isEqualTo("protocol"); + assertThat(request.getOffer().getId()).isEqualTo("offerId"); + assertThat(request.getOffer().getPolicy()).isNotNull(); + assertThat(request.getCallbackAddresses()).hasSize(1); + } + + @Test + void verify_transfor_withNoProviderId() { + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + // do not set provider ID + .offer(createOffer("offerId", "assetId")) + .build(); + + var request = transformer.transform(dto, context); + + assertThat(request).isNotNull(); + assertThat(request.getOffer().getProviderId()).asString().isEqualTo(dto.getConnectorAddress()); + } + + @Test + void verify_transform_withNoConsumerId() { + var dto = NegotiateEdrRequestDto.Builder.newInstance() + .connectorId("connectorId") + .connectorAddress("address") + .protocol("protocol") + // do not set consumer ID + .providerId("urn:connector:test-provider") + .offer(createOffer("offerId", "assetId")) + .build(); + + var request = transformer.transform(dto, context); + assertThat(request).isNotNull(); + assertThat(request.getOffer().getProviderId()).asString().isEqualTo("urn:connector:test-provider"); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/build.gradle.kts b/edc-extensions/control-plane-adapter-callback/build.gradle.kts new file mode 100644 index 000000000..ffb126fed --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + implementation(project(":spi:control-plane-adapter-spi")) + implementation(project(":spi:edr-cache-spi")) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.controlplane) + implementation(libs.edc.spi.aggregateservices) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java new file mode 100644 index 000000000..0220b1330 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImpl.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequestData; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class AdapterTransferProcessServiceImpl implements AdapterTransferProcessService { + + public static final String LOCAL_ADAPTER_URI = "local://adapter"; + public static final Set LOCAL_EVENTS = Set.of("contract.negotiation", "transfer.process"); + public static final CallbackAddress LOCAL_CALLBACK = CallbackAddress.Builder.newInstance() + .transactional(true) + .uri(LOCAL_ADAPTER_URI) + .events(LOCAL_EVENTS) + .build(); + private final ContractNegotiationService contractNegotiationService; + + public AdapterTransferProcessServiceImpl(ContractNegotiationService contractNegotiationService) { + this.contractNegotiationService = contractNegotiationService; + } + + @Override + public ServiceResult initiateEdrNegotiation(NegotiateEdrRequest request) { + var contractNegotiation = contractNegotiationService.initiateNegotiation(createContractRequest(request)); + return ServiceResult.success(contractNegotiation); + } + + private ContractRequest createContractRequest(NegotiateEdrRequest request) { + var callbacks = Stream.concat(request.getCallbackAddresses().stream(), Stream.of(LOCAL_CALLBACK)).collect(Collectors.toList()); + + var requestData = ContractRequestData.Builder.newInstance() + .contractOffer(request.getOffer()) + .protocol(request.getProtocol()) + .counterPartyAddress(request.getConnectorAddress()) + .connectorId(request.getConnectorId()) + .build(); + + return ContractRequest.Builder.newInstance() + .requestData(requestData) + .callbackAddresses(callbacks).build(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java new file mode 100644 index 000000000..80082a366 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; + +import java.util.UUID; + +import static java.lang.String.format; + +public class ContractNegotiationCallback implements InProcessCallback { + + public static final DataAddress DATA_DESTINATION = DataAddress.Builder.newInstance().type("HttpProxy").build(); + private final TransferProcessService transferProcessService; + + private final Monitor monitor; + + public ContractNegotiationCallback(TransferProcessService transferProcessService, Monitor monitor) { + this.transferProcessService = transferProcessService; + this.monitor = monitor; + } + + @Override + public Result invoke(CallbackEventRemoteMessage message) { + if (message.getEventEnvelope().getPayload() instanceof ContractNegotiationFinalized) { + return initiateTransfer((ContractNegotiationFinalized) message.getEventEnvelope().getPayload()); + } + return Result.success(); + } + + private Result initiateTransfer(ContractNegotiationFinalized negotiationFinalized) { + + var dataRequest = + DataRequest.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .assetId(negotiationFinalized.getContractAgreement().getAssetId()) + .contractId(negotiationFinalized.getContractAgreement().getId()) + .connectorId(negotiationFinalized.getCounterPartyId()) + .connectorAddress(negotiationFinalized.getCounterPartyAddress()) + .protocol(negotiationFinalized.getProtocol()) + .dataDestination(DATA_DESTINATION) + .managedResources(false) + .build(); + + var transferRequest = TransferRequest.Builder.newInstance() + .dataRequest(dataRequest) + .callbackAddresses(negotiationFinalized.getCallbackAddresses()) + .build(); + + var result = transferProcessService.initiateTransfer(transferRequest); + + if (result.failed()) { + var msg = format("Failed to initiate a transfer for contract %s and asset %s, error: %s", negotiationFinalized.getContractAgreement().getId(), negotiationFinalized.getContractAgreement().getAssetId(), result.getFailureDetail()); + monitor.severe(msg); + return Result.failure(msg); + } + monitor.debug(format("Transfer with id %s initiated", result.getContent())); + return Result.success(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java new file mode 100644 index 000000000..4b0130b24 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcher.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.message.RemoteMessageDispatcher; +import org.eclipse.edc.spi.types.domain.message.RemoteMessage; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +import java.util.concurrent.CompletableFuture; + +import static java.lang.String.format; + +public class InProcessCallbackMessageDispatcher implements RemoteMessageDispatcher { + + public static final String CALLBACK_EVENT_LOCAL = "callback-event-local"; + + private final InProcessCallbackRegistry registry; + + public InProcessCallbackMessageDispatcher(InProcessCallbackRegistry registry) { + this.registry = registry; + } + + @Override + public String protocol() { + return CALLBACK_EVENT_LOCAL; + } + + @Override + public CompletableFuture send(Class responseType, M message) { + if (message instanceof CallbackEventRemoteMessage) { + var result = registry.handleMessage((CallbackEventRemoteMessage) message); + if (result.succeeded()) { + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.failedFuture(new EdcException(result.getFailureDetail())); + } + } + return CompletableFuture.failedFuture(new EdcException(format("Message of type %s not supported", message.getClass().getSimpleName()))); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java new file mode 100644 index 000000000..f7bb6ba06 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtension.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +@Extension(InProcessCallbackRegistryExtension.NAME) +public class InProcessCallbackRegistryExtension implements ServiceExtension { + + public static final String NAME = "In process callback registry extension"; + + @Override + public String name() { + return NAME; + } + + @Provider + public InProcessCallbackRegistry callbackRegistry() { + return new InProcessCallbackRegistryImpl(); + } + +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java new file mode 100644 index 000000000..d65c4e5ae --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; + +import java.util.ArrayList; +import java.util.List; + +public class InProcessCallbackRegistryImpl implements InProcessCallbackRegistry { + + private final List handlers = new ArrayList<>(); + + @Override + public void registerHandler(InProcessCallback callback) { + handlers.add(callback); + } + + @Override + public Result handleMessage(CallbackEventRemoteMessage message) { + return handlers.stream() + .map(handler -> handler.invoke(message)) + .filter(Result::failed) + .findFirst() + .orElseGet(Result::success); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java new file mode 100644 index 000000000..4fe11df5d --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtension.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolverRegistry; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; + +import static org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackMessageDispatcher.CALLBACK_EVENT_LOCAL; + +@Provides(AdapterTransferProcessService.class) +@Extension(LocalCallbackExtension.NAME) +public class LocalCallbackExtension implements ServiceExtension { + public static final String NAME = "Local callbacks extension"; + + public static final String LOCAL = "local"; + @Inject + private RemoteMessageDispatcherRegistry registry; + + @Inject + private CallbackProtocolResolverRegistry resolverRegistry; + + @Inject + private TransferProcessService transferProcessService; + + @Inject + private ContractNegotiationService contractNegotiationService; + + @Inject + private TransferProcessStore transferProcessStore; + + @Inject + private EndpointDataReferenceCache edrCache; + + @Inject + private InProcessCallbackRegistry callbackRegistry; + + @Inject + private Monitor monitor; + + @Inject + private TransactionContext transactionContext; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + + callbackRegistry.registerHandler(new ContractNegotiationCallback(transferProcessService, monitor)); + callbackRegistry.registerHandler(new TransferProcessLocalCallback(edrCache, transferProcessStore, transactionContext)); + + resolverRegistry.registerResolver(this::resolveProtocol); + registry.register(new InProcessCallbackMessageDispatcher(callbackRegistry)); + + context.registerService(AdapterTransferProcessService.class, new AdapterTransferProcessServiceImpl(contractNegotiationService)); + } + + private String resolveProtocol(String scheme) { + + if (scheme.equalsIgnoreCase(LOCAL)) { + return CALLBACK_EVENT_LOCAL; + } + return null; + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java new file mode 100644 index 000000000..6d450bfd8 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallback.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataAddressConstants; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; + +import static java.lang.String.format; + +public class TransferProcessLocalCallback implements InProcessCallback { + + private final EndpointDataReferenceCache edrCache; + private final TransferProcessStore transferProcessStore; + + private final TransactionContext transactionContext; + + public TransferProcessLocalCallback(EndpointDataReferenceCache edrCache, TransferProcessStore transferProcessStore, TransactionContext transactionContext) { + this.edrCache = edrCache; + this.transferProcessStore = transferProcessStore; + this.transactionContext = transactionContext; + } + + @Override + public Result invoke(CallbackEventRemoteMessage message) { + if (message.getEventEnvelope().getPayload() instanceof TransferProcessStarted) { + var transferProcessStarted = (TransferProcessStarted) message.getEventEnvelope().getPayload(); + if (transferProcessStarted.getDataAddress() != null) { + return EndpointDataAddressConstants.to(transferProcessStarted.getDataAddress()) + .compose(this::storeEdr) + .mapTo(); + } + } + return Result.success(); + } + + private Result storeEdr(EndpointDataReference edr) { + return transactionContext.execute(() -> { + // TODO upstream api for getting the TP with the DataRequest#id + var transferProcessId = transferProcessStore.processIdForDataRequestId(edr.getId()); + var transferProcess = transferProcessStore.findById(transferProcessId); + if (transferProcess != null) { + var cacheEntry = EndpointDataReferenceEntry.Builder.newInstance(). + transferProcessId(transferProcess.getId()) + .assetId(transferProcess.getDataRequest().getAssetId()) + .agreementId(transferProcess.getDataRequest().getContractId()) + .build(); + + edrCache.save(cacheEntry, edr); + return Result.success(); + } else { + return Result.failure(format("Failed to find a transfer process with ID %s", transferProcessId)); + } + }); + + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..210521e83 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,16 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# +# Contributors: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackRegistryExtension +org.eclipse.tractusx.edc.cp.adapter.callback.LocalCallbackExtension diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java new file mode 100644 index 000000000..1e95f0e1c --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/AdapterTransferProcessServiceImplTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.AdapterTransferProcessServiceImpl.LOCAL_CALLBACK; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AdapterTransferProcessServiceImplTest { + + ContractNegotiationService contractNegotiationService = mock(ContractNegotiationService.class); + + @Test + void initEdrNegotiation_shouldFireAContractNegotiation_WhenUsingCallbacks() { + var transferService = new AdapterTransferProcessServiceImpl(contractNegotiationService); + + var captor = ArgumentCaptor.forClass(ContractRequest.class); + + when(contractNegotiationService.initiateNegotiation(any())).thenReturn(getContractNegotiation()); + + var negotiateEdrRequest = getNegotiateEdrRequest(); + + var result = transferService.initiateEdrNegotiation(negotiateEdrRequest); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent()).isNotNull(); + + verify(contractNegotiationService).initiateNegotiation(captor.capture()); + + var msg = captor.getValue(); + + assertThat(msg.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().containsAll(negotiateEdrRequest.getCallbackAddresses()); + assertThat(msg.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().contains(LOCAL_CALLBACK); + assertThat(msg.getRequestData().getContractOffer()).usingRecursiveComparison().isEqualTo(negotiateEdrRequest.getOffer()); + assertThat(msg.getRequestData().getProtocol()).isEqualTo(negotiateEdrRequest.getProtocol()); + assertThat(msg.getRequestData().getCounterPartyAddress()).isEqualTo(negotiateEdrRequest.getConnectorAddress()); + + } + + private NegotiateEdrRequest getNegotiateEdrRequest() { + return NegotiateEdrRequest.Builder.newInstance() + .protocol("protocol") + .connectorAddress("http://test") + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri("test").events(Set.of("test")).build())) + .offer(ContractOffer.Builder.newInstance() + .id("id") + .assetId("assetId") + .policy(Policy.Builder.newInstance().build()) + .providerId("provider") + .build()) + .build(); + } + + private ContractNegotiation getContractNegotiation() { + return ContractNegotiation.Builder.newInstance() + .id("id") + .counterPartyAddress("http://test") + .counterPartyId("provider") + .protocol("protocol") + .build(); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java new file mode 100644 index 000000000..d24f05382 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/ContractNegotiationCallbackTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationConfirmed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationDeclined; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFailed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationOffered; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationRequested; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationTerminated; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; +import org.eclipse.edc.connector.transfer.spi.types.TransferRequest; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.ContractNegotiationCallback.DATA_DESTINATION; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getNegotiationFinalizedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + + +public class ContractNegotiationCallbackTest { + + TransferProcessService transferProcessService = mock(TransferProcessService.class); + + Monitor monitor = mock(Monitor.class); + + ContractNegotiationCallback callback; + + @BeforeEach + void setup() { + callback = new ContractNegotiationCallback(transferProcessService, monitor); + } + + @Test + void invoke_shouldStartATransferProcess() { + + var captor = ArgumentCaptor.forClass(TransferRequest.class); + + when(transferProcessService.initiateTransfer(any())).thenReturn(ServiceResult.success(TransferProcess.Builder.newInstance().id("test").build())); + + var event = getNegotiationFinalizedEvent(); + var message = remoteMessage(event); + + var result = callback.invoke(message); + + assertThat(result.succeeded()).isTrue(); + verify(transferProcessService).initiateTransfer(captor.capture()); + + + var tp = captor.getValue(); + + assertThat(tp.getCallbackAddresses()).usingRecursiveFieldByFieldElementComparator().containsAll(event.getCallbackAddresses()); + + assertThat(tp.getDataRequest()).satisfies(dataRequest -> { + + assertThat(dataRequest.getContractId()).isEqualTo(event.getContractAgreement().getId()); + assertThat(dataRequest.getAssetId()).isEqualTo(event.getContractAgreement().getAssetId()); + assertThat(dataRequest.getConnectorAddress()).isEqualTo(event.getCounterPartyAddress()); + assertThat(dataRequest.getConnectorId()).isEqualTo(event.getCounterPartyId()); + assertThat(dataRequest.getProtocol()).isEqualTo(event.getProtocol()); + assertThat(dataRequest.getDataDestination()).usingRecursiveComparison().isEqualTo(DATA_DESTINATION); + }); + + } + + @Test + void invoke_shouldThrowException_whenATransferRequestFails() { + + when(transferProcessService.initiateTransfer(any())).thenReturn(ServiceResult.badRequest("test")); + + var event = getNegotiationFinalizedEvent(); + var message = remoteMessage(event); + + + var result = callback.invoke(message); + + assertThat(result.failed()).isTrue(); + + } + + @ParameterizedTest + @ArgumentsSource(EventInstances.class) + void invoke_shouldIgnoreOtherEvents(ContractNegotiationEvent event) { + var message = remoteMessage(event); + callback.invoke(message); + + verifyNoInteractions(transferProcessService); + } + + private static class EventInstances implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + baseBuilder(ContractNegotiationAccepted.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationConfirmed.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationDeclined.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationFailed.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationInitiated.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationOffered.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationRequested.Builder.newInstance()).build(), + baseBuilder(ContractNegotiationTerminated.Builder.newInstance()).build() + ).map(Arguments::of); + + } + + private > B baseBuilder(B builder) { + var callbacks = List.of(CallbackAddress.Builder.newInstance().uri("http://local").events(Set.of("test")).build()); + return builder + .contractNegotiationId("id") + .protocol("test") + .callbackAddresses(callbacks) + .counterPartyAddress("addr") + .counterPartyId("provider"); + } + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java new file mode 100644 index 000000000..27ef6565e --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackMessageDispatcherTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.message.RemoteMessage; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getNegotiationFinalizedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class InProcessCallbackMessageDispatcherTest { + + InProcessCallback callback = mock(InProcessCallback.class); + + InProcessCallbackMessageDispatcher dispatcher; + + @BeforeEach + void setup() { + var registry = new InProcessCallbackRegistryImpl(); + registry.registerHandler(callback); + dispatcher = new InProcessCallbackMessageDispatcher(registry); + } + + @Test + void send_shouldInvokeRegisteredCallback() { + + var msg = remoteMessage(getNegotiationFinalizedEvent()); + when(callback.invoke(any())).thenReturn(Result.success()); + dispatcher.send(Object.class, msg).join(); + + + verify(callback).invoke(msg); + } + + @Test + void send_shouldNotInvokeRegisteredCallback_whenItsNotACallbackRemoteMessage() { + + assertThatThrownBy(() -> dispatcher.send(Object.class, new TestMessage()).join()) + .hasCauseInstanceOf(EdcException.class); + + + verifyNoInteractions(callback); + } + + private static class TestMessage implements RemoteMessage { + + @Override + public String getProtocol() { + return "test"; + } + + @Override + public String getCounterPartyAddress() { + return "test"; + } + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java new file mode 100644 index 000000000..9aef1a0b2 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/InProcessCallbackRegistryExtensionTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(DependencyInjectionExtension.class) +public class InProcessCallbackRegistryExtensionTest { + + InProcessCallbackRegistryExtension extension; + + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + extension = factory.constructInstance(InProcessCallbackRegistryExtension.class); + } + + @Test + void shouldInitializeTheExtension(ServiceExtensionContext context) { + assertThat(extension.callbackRegistry()).isInstanceOf(InProcessCallbackRegistryImpl.class); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java new file mode 100644 index 000000000..8e68c0ed1 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/LocalCallbackExtensionTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolver; +import org.eclipse.edc.connector.spi.callback.CallbackProtocolResolverRegistry; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallback; +import org.eclipse.tractusx.edc.spi.cp.adapter.callback.InProcessCallbackRegistry; +import org.eclipse.tractusx.edc.spi.cp.adapter.service.AdapterTransferProcessService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.InProcessCallbackMessageDispatcher.CALLBACK_EVENT_LOCAL; +import static org.mockito.Mockito.*; + +@ExtendWith(DependencyInjectionExtension.class) +public class LocalCallbackExtensionTest { + + LocalCallbackExtension extension; + + RemoteMessageDispatcherRegistry dispatcherRegistry = mock(RemoteMessageDispatcherRegistry.class); + + CallbackProtocolResolverRegistry resolverRegistry = mock(CallbackProtocolResolverRegistry.class); + + InProcessCallbackRegistry inProcessCallbackRegistry = mock(InProcessCallbackRegistry.class); + + @BeforeEach + void setUp(ObjectFactory factory, ServiceExtensionContext context) { + + context.registerService(RemoteMessageDispatcherRegistry.class, dispatcherRegistry); + context.registerService(CallbackProtocolResolverRegistry.class, resolverRegistry); + context.registerService(InProcessCallbackRegistry.class, inProcessCallbackRegistry); + extension = factory.constructInstance(LocalCallbackExtension.class); + } + + @Test + void shouldInitializeTheExtension(ServiceExtensionContext context) { + extension.initialize(context); + + var captor = ArgumentCaptor.forClass(CallbackProtocolResolver.class); + verify(resolverRegistry).registerResolver(captor.capture()); + + var resolver = captor.getValue(); + assertThat(resolver.resolve("local")).isEqualTo(CALLBACK_EVENT_LOCAL); + assertThat(resolver.resolve("test")).isNull(); + + + var service = context.getService(AdapterTransferProcessService.class); + assertThat(service).isInstanceOf(AdapterTransferProcessServiceImpl.class); + + var callbackArgumentCaptor = ArgumentCaptor.forClass(InProcessCallback.class); + verify(inProcessCallbackRegistry, times(2)).registerHandler(callbackArgumentCaptor.capture()); + + assertThat(callbackArgumentCaptor.getAllValues()) + .flatExtracting(Object::getClass) + .containsExactly(ContractNegotiationCallback.class, TransferProcessLocalCallback.class); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java new file mode 100644 index 000000000..ec8593667 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TestFunctions.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.event.EventEnvelope; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class TestFunctions { + + public static ContractNegotiationFinalized getNegotiationFinalizedEvent() { + var agreement = ContractAgreement.Builder.newInstance() + .id("id") + .policy(Policy.Builder.newInstance().build()) + .assetId("assetId") + .consumerId("consumer") + .providerId("provider") + .build(); + + return ContractNegotiationFinalized.Builder.newInstance() + .contractNegotiationId("id") + .protocol("test-protocol") + .counterPartyId("counter-party") + .counterPartyAddress("https://counter-party") + .contractAgreement(agreement) + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test")) + .transactional(true) + .build())) + .build(); + } + + public static TransferProcessStarted getTransferProcessStartedEvent() { + return getTransferProcessStartedEvent(null); + } + + public static TransferProcessStarted getTransferProcessStartedEvent(DataAddress dataAddress) { + return TransferProcessStarted.Builder.newInstance() + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test")) + .transactional(true) + .build())) + .dataAddress(dataAddress) + .transferProcessId(UUID.randomUUID().toString()) + .build(); + } + + public static EndpointDataReference getEdr() { + return EndpointDataReference.Builder.newInstance() + .id("dataRequestId") + .authCode("authCode") + .authKey("authKey") + .endpoint("http://endpoint") + .build(); + } + + public static CallbackEventRemoteMessage remoteMessage(T event) { + var callback = CallbackAddress.Builder.newInstance() + .events(Set.of("test")) + .uri("local://test") + .build(); + + var envelope = EventEnvelope.Builder + .newInstance() + .id(UUID.randomUUID().toString()) + .at(System.currentTimeMillis()) + .payload(event) + .build(); + return new CallbackEventRemoteMessage(callback, envelope, "local"); + } +} diff --git a/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java new file mode 100644 index 000000000..b4b6d8480 --- /dev/null +++ b/edc-extensions/control-plane-adapter-callback/src/test/java/org/eclipse/tractusx/edc/cp/adapter/callback/TransferProcessLocalCallbackTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.cp.adapter.callback; + +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessCompleted; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessDeprovisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessEvent; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessProvisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessRequested; +import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; +import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataAddressConstants; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.edc.transaction.spi.NoopTransactionContext; +import org.eclipse.edc.transaction.spi.TransactionContext; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.mockito.ArgumentCaptor; + +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getEdr; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.getTransferProcessStartedEvent; +import static org.eclipse.tractusx.edc.cp.adapter.callback.TestFunctions.remoteMessage; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + + +public class TransferProcessLocalCallbackTest { + + TransferProcessStore transferProcessStore = mock(TransferProcessStore.class); + EndpointDataReferenceCache edrCache = mock(EndpointDataReferenceCache.class); + + TransactionContext transactionContext = new NoopTransactionContext(); + + TransferProcessLocalCallback callback; + + + @BeforeEach + void setup() { + callback = new TransferProcessLocalCallback(edrCache, transferProcessStore, transactionContext); + } + + @Test + void invoke_shouldStoreTheEdrInCache_whenDataAddressIsPresent() { + + var transferProcessId = "transferProcessId"; + var assetId = "assetId"; + var contractId = "contractId"; + + + var edr = getEdr(); + + when(transferProcessStore.processIdForDataRequestId(edr.getId())).thenReturn(transferProcessId); + + var dataRequest = DataRequest.Builder.newInstance().id(edr.getId()) + .destinationType("HttpProxy") + .assetId(assetId) + .contractId(contractId) + .build(); + + var transferProcess = TransferProcess.Builder.newInstance() + .id(transferProcessId) + .dataRequest(dataRequest) + .build(); + + when(transferProcessStore.findById(transferProcessId)).thenReturn(transferProcess); + + + var event = getTransferProcessStartedEvent(EndpointDataAddressConstants.from(edr)); + + var cacheEntryCaptor = ArgumentCaptor.forClass(EndpointDataReferenceEntry.class); + var edrCaptor = ArgumentCaptor.forClass(EndpointDataReference.class); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isTrue(); + + verify(edrCache).save(cacheEntryCaptor.capture(), edrCaptor.capture()); + + assertThat(edrCaptor.getValue()).usingRecursiveComparison().isEqualTo(edr); + + } + + @Test + void invoke_shouldNotFail_whenDataAddressIsAbsent() { + + var event = getTransferProcessStartedEvent(); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isTrue(); + + verifyNoInteractions(edrCache); + verifyNoInteractions(transferProcessStore); + } + + @Test + void invoke_shouldNotFail_whenTransferProcessNotFound() { + + var transferProcessId = "transferProcessId"; + + var edr = getEdr(); + + when(transferProcessStore.processIdForDataRequestId(edr.getId())).thenReturn(transferProcessId); + + when(transferProcessStore.findById(transferProcessId)).thenReturn(null); + + var event = getTransferProcessStartedEvent(EndpointDataAddressConstants.from(edr)); + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.succeeded()).isFalse(); + + verifyNoInteractions(edrCache); + } + + @Test + void invoke_shouldFail_withInvalidDataAddress() { + + var event = getTransferProcessStartedEvent(DataAddress.Builder.newInstance().type("HttpProxy").build()); + + var message = remoteMessage(event); + + var result = callback.invoke(message); + assertThat(result.failed()).isTrue(); + + verifyNoInteractions(edrCache); + verifyNoInteractions(transferProcessStore); + } + + @ParameterizedTest + @ArgumentsSource(EventInstances.class) + void invoke_shouldIgnoreOtherEvents(TransferProcessEvent event) { + var message = remoteMessage(event); + var result = callback.invoke(message); + + assertThat(result.succeeded()).isTrue(); + + verifyNoInteractions(edrCache); + } + + private static class EventInstances implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + baseBuilder(TransferProcessRequested.Builder.newInstance()).build(), + baseBuilder(TransferProcessProvisioned.Builder.newInstance()).build(), + baseBuilder(TransferProcessCompleted.Builder.newInstance()).build(), + baseBuilder(TransferProcessDeprovisioned.Builder.newInstance()).build() + ).map(Arguments::of); + + } + + private > B baseBuilder(B builder) { + var callbacks = List.of(CallbackAddress.Builder.newInstance().uri("http://local").events(Set.of("test")).build()); + return builder + .transferProcessId(UUID.randomUUID().toString()) + .callbackAddresses(callbacks); + } + } +} diff --git a/edc-extensions/control-plane-adapter/README.md b/edc-extensions/control-plane-adapter/README.md deleted file mode 100644 index 5f2d0d890..000000000 --- a/edc-extensions/control-plane-adapter/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Control Plane Adapter Extension - -The goal of this extension is to simplify the process of retrieving data out of EDC. It returns an `EndpointDataReference` object, hiding all the communication details for contract offers, contract negotiation process and retrieving `EndpointDataReference` from EDC controlplane. - -Additional requirements, that affects the architecture of the extension: - -- can return data both in SYNC and ASYNC mode (currently only SYNC endpoint available) -- can be persistent, so that process can be restored from the point where it was before application was stopped -- scaling horizontally (when persistence is added to configuration) -- can retry failed part of the process (no need to start the process from the beginning) - -## Configuration - -| Key | Description | Mandatory | Default | -|:---------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|---------| -| `edc.cp.adapter.default.message.retry.number` | Number of retries of a message, in case of an error, within the internal process of retrieving DataReference | no | 3 | -| `edc.cp.adapter.default.sync.request.timeout` | Timeout for synchronous request (in seconds), after witch 'timeout' error will be returned to the requesting client | no | 20 | -| `edc.cp.adapter.messagebus.inmemory.thread.number` | Number of threads running within the in-memory implementation of MessageBus | no | 10 | -| `edc.cp.adapter.reuse.contract.agreement` | Turn on/off reusing of existing contract agreements for the specific asset. Once the contract is agreed, the second request for the same asset will reuse the agreement (if exists) pulled from the EDC. | no | true | -| `edc.cp.adapter.cache.catalog.expire.after` | Number of seconds, after witch previously requested catalog will not be reused, and will be removed from catalog cache | no | 300 | -| `edc.cp.adapter.catalog.request.limit` | Maximum number of items taken from Catalog within single request. Requests are repeated until all offers of the query are retrieved | no | 100 | - -By default, the extension works in "IN MEMORY" mode. This setup has some limitations: - -- It can work only within single EDC instance. If CP-adapter requests are handled by more than one EDC, data flow may be broken. -- If the EDC instance is restarted, all running processes are lost. - -To run CP-Adapter in "PERSISTENT" mode, You need to create a proper tables with [this](docs/schema.sql) script, and add the following configuration values to your controlplane EDC properties file: - -| Key | Description | -|-------------------------------------|----------------------| -| `edc.datasource.cpadapter.name` | data source name | -| `edc.datasource.cpadapter.url` | data source url | -| `edc.datasource.cpadapter.user` | data source user | -| `edc.datasource.cpadapter.password` | data source password | - -## How to use it - -1. Client sends a GET request with two parameters: assetId and the url of the provider controlplane: - - ```plain - {controlplaneUrl}:{web.http.management.port}/{web.http.management.path}/adapter/asset/sync/{assetId}?providerUrl={providerUrl} - ``` - - | Name | Description | - |----------------------------|----------------------------------------------------------------------------------| - | `controlplaneUrl` | The URL where the control plane of the consumer connector is available | - | `web.http.management.port` | Port of the management API provided by the control plane | - | `web.http.management.path` | Path of the management API provided by the control plane | - | `assetId` | ID of the wanted asset | - | `providerUrl` | URL pointing to the `data` endpoint of the IDS context of the provider connector | - - The example ULR could be: - - ```plain - http://localhost:9193/api/v1/data/adapter/asset/sync/123?providerUrl=http://localhost:8182/api/v1/ids/data - ``` - - Optional request parameters, that overwrite the settings for a single request: - - | Name | Description | - |--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | `contractAgreementId` | Defines the ID of existing contract agreement, that should be reused for retrieving the asset. If parameter is specified, but contract is not found, 404 error will be returned. | - | `contractAgreementReuse` | Similar to `edc.cp.adapter.reuse.contract.agreement` option allows to turn off reusing of existing contracts, but on a request level. Set the parameter value to 'false' and new contract agrement will be negotiated. | - | `timeout` | Similar to `edc.cp.adapter.default.sync.request.timeout`, defines the maximum time of the request. If data is not ready, time out error will be returned. | - - The controller is registered under the context alias of the Management API. The authentication depends on the configuration of the Management API. - To find out more please visit: - - - [Management API Documentation](https://github.com/eclipse-edc/Connector/tree/main/extensions/control-plane/api/management-api) - - [Management API Configuration Extension](https://github.com/eclipse-edc/Connector/tree/main/extensions/common/api/management-api-configuration) - -2. `EndpointDataReference` object is returned. Example of the `EndpointDataReference` response: - - ```json - { - "id": "ee8b758a-4b02-4cca-bb37-d0256b4638e7", - "endpoint": "http://consumer-dataplane:9192/publicsubmodel?provider-connector-url=...", - "authKey": "Authorization", - "authCode": "eyJhbGciOiJSUzI1NiJ9.eyJkYWQiOi...", - "properties": { - "cid": "1:b2367617-5f51-48c5-9f25-e30a7299235c" - } - } - ``` - -3. Client, using the `EndpointDataReference`, retrieves the Asset through dataplane. - - Example of the dataplane GET request, to retrieve Asset, with `EndpointDataReference` information: - - ```plain - url: http://consumer-dataplane:9192/publicsubmodel?provider-connector-url=... {endpoint} - header: Authorization:eyJhbGciOiJSUzI1NiJ9.eyJkYWQiOi... {authKey:authCode} - ``` - -### Internal design of the extension - -![diagram](src/main/resources/control-plane-adapter.jpg) diff --git a/edc-extensions/control-plane-adapter/build.gradle.kts b/edc-extensions/control-plane-adapter/build.gradle.kts deleted file mode 100644 index f22dd2e5f..000000000 --- a/edc-extensions/control-plane-adapter/build.gradle.kts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -plugins { - `java-library` - `maven-publish` - id("io.swagger.core.v3.swagger-gradle-plugin") -} - -dependencies { - implementation(edc.spi.core) - implementation(edc.spi.policy) - implementation(edc.api.management) - implementation(edc.spi.catalog) - implementation(edc.spi.transactionspi) - implementation(edc.spi.transaction.datasource) - implementation(edc.ids) - implementation(edc.sql.core) - implementation(edc.sql.lease) - implementation(edc.sql.pool) - - - implementation(libs.postgres) - implementation(libs.jakarta.rsApi) - - - implementation(edc.spi.aggregateservices) - testImplementation(libs.awaitility) -} diff --git a/edc-extensions/control-plane-adapter/docs/schema.sql b/edc-extensions/control-plane-adapter/docs/schema.sql deleted file mode 100644 index 3e64d3c45..000000000 --- a/edc-extensions/control-plane-adapter/docs/schema.sql +++ /dev/null @@ -1,54 +0,0 @@ --- --- Copyright (c) 2022 ZF Friedrichshafen AG --- --- This program and the accompanying materials are made available under the --- terms of the Apache License, Version 2.0 which is available at --- https://www.apache.org/licenses/LICENSE-2.0 --- --- SPDX-License-Identifier: Apache-2.0 --- --- Contributors: --- ZF Friedrichshafen AG - Initial SQL Query --- - --- Statements are designed for and tested with Postgres only! - - -CREATE TABLE IF NOT EXISTS edc_lease -( - leased_by VARCHAR NOT NULL, - leased_at BIGINT, - lease_duration INTEGER NOT NULL, - lease_id VARCHAR NOT NULL - CONSTRAINT lease_pk - PRIMARY KEY -); - -CREATE TABLE IF NOT EXISTS edc_cpadapter_queue -( - id VARCHAR NOT NULL, - created_at BIGINT NOT NULL, - channel VARCHAR, - message JSON, - invoke_after BIGINT NOT NULL, - lease_id VARCHAR - CONSTRAINT cpadapter_queue_lease_lease_id_fk - REFERENCES edc_lease - ON DELETE SET NULL, - PRIMARY KEY (id) -); - -CREATE UNIQUE INDEX IF NOT EXISTS edc_cpadapter_queue_id_uindex - ON edc_cpadapter_queue (id); - -CREATE TABLE IF NOT EXISTS edc_cpadapter_object_store -( - id VARCHAR NOT NULL, - created_at BIGINT NOT NULL, - type VARCHAR, - object JSON, - PRIMARY KEY (id) -); - - - diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterConfig.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterConfig.java deleted file mode 100644 index cd243f9ba..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter; - -import org.eclipse.edc.spi.system.ServiceExtensionContext; - -public class ApiAdapterConfig { - private static final String DEFAULT_MESSAGE_RETRY_NUMBER = - "edc.cp.adapter.default.message.retry.number"; - private static final String DEFAULT_SYNC_REQUEST_TIMEOUT = - "edc.cp.adapter.default.sync.request.timeout"; - private static final String CATALOG_EXPIRE_AFTER_TIME = - "edc.cp.adapter.cache.catalog.expire.after"; - private static final String REUSE_CONTRACT_AGREEMENT = "edc.cp.adapter.reuse.contract.agreement"; - private static final String CATALOG_REQUEST_LIMIT = "edc.cp.adapter.catalog.request.limit"; - - private static final String DATASOURCE_NAME = "edc.datasource.cpadapter.name"; - private static final String DATASOURCE_URL = "edc.datasource.cpadapter.url"; - private static final String DATASOURCE_USER = "edc.datasource.cpadapter.user"; - private static final String DATASOURCE_PASS = "edc.datasource.cpadapter.password"; - - private static final String IN_MEMORY_MESSAGE_BUS_THREAD_NUMBER = - "edc.cp.adapter.messagebus.inmemory.thread.number"; - private static final String SQL_MESSAGE_BUS_THREAD_NUMBER = - "edc.cp.adapter.messagebus.sql.thread.number"; - private static final String SQL_MESSAGE_BUS_MAX_DELIVERY = - "edc.cp.adapter.messagebus.sql.max.delivery"; - private static final String SQL_MESSAGE_BUS_DELIVERY_INTERVAL = - "edc.cp.adapter.messagebus.sql.max.delivery"; - - private final ServiceExtensionContext context; - - public ApiAdapterConfig(ServiceExtensionContext context) { - this.context = context; - } - - public int getDefaultMessageRetryNumber() { - return context.getSetting(DEFAULT_MESSAGE_RETRY_NUMBER, 3); - } - - public int getDefaultSyncRequestTimeout() { - return context.getSetting(DEFAULT_SYNC_REQUEST_TIMEOUT, 20); - } - - public int getInMemoryMessageBusThreadNumber() { - return context.getSetting(IN_MEMORY_MESSAGE_BUS_THREAD_NUMBER, 10); - } - - public boolean isContractAgreementReuseOn() { - return context.getSetting(REUSE_CONTRACT_AGREEMENT, true); - } - - public int getCatalogExpireAfterTime() { - return context.getSetting(CATALOG_EXPIRE_AFTER_TIME, 180); - } - - public int getCatalogRequestLimit() { - return context.getSetting(CATALOG_REQUEST_LIMIT, 100); - } - - public String getDataSourceName() { - return context.getSetting(DATASOURCE_NAME, "cpadapter"); - } - - public String getDataSourceUrl() { - return context.getSetting(DATASOURCE_URL, null); - } - - public String getDataSourceUser() { - return context.getSetting(DATASOURCE_USER, null); - } - - public String getDataSourcePass() { - return context.getSetting(DATASOURCE_PASS, null); - } - - public int getSqlMessageBusThreadNumber() { - return context.getSetting(SQL_MESSAGE_BUS_THREAD_NUMBER, 10); - } - - public int getSqlMessageBusMaxDelivery() { - return context.getSetting(SQL_MESSAGE_BUS_MAX_DELIVERY, 10); - } - - public int getSqlMessageBusDeliveryInterval() { - return context.getSetting(SQL_MESSAGE_BUS_DELIVERY_INTERVAL, 1); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterExtension.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterExtension.java deleted file mode 100644 index 16eb0a32a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/ApiAdapterExtension.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter; - -import static java.util.Objects.nonNull; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.Clock; -import java.util.Objects; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.eclipse.edc.connector.api.management.configuration.ManagementApiConfiguration; -import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener; -import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationObservable; -import org.eclipse.edc.connector.spi.catalog.CatalogService; -import org.eclipse.edc.connector.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.transfer.spi.edr.EndpointDataReferenceReceiver; -import org.eclipse.edc.connector.transfer.spi.edr.EndpointDataReferenceReceiverRegistry; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; -import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.tractusx.edc.cp.adapter.messaging.*; -import org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation.CatalogCachedRetriever; -import org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation.CatalogRetriever; -import org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation.ContractAgreementRetriever; -import org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation.ContractNegotiationHandler; -import org.eclipse.tractusx.edc.cp.adapter.process.contractnotification.*; -import org.eclipse.tractusx.edc.cp.adapter.process.datareference.*; -import org.eclipse.tractusx.edc.cp.adapter.service.ErrorResultService; -import org.eclipse.tractusx.edc.cp.adapter.service.ResultService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreServiceInMemory; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreServiceSql; -import org.eclipse.tractusx.edc.cp.adapter.store.SqlObjectStore; -import org.eclipse.tractusx.edc.cp.adapter.store.SqlQueueStore; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.postgres.PostgresDialectObjectStoreStatements; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.postgres.PostgresDialectQueueStatements; -import org.eclipse.tractusx.edc.cp.adapter.util.ExpiringMap; -import org.eclipse.tractusx.edc.cp.adapter.util.LockMap; - -public class ApiAdapterExtension implements ServiceExtension { - @Inject private Monitor monitor; - @Inject private ContractNegotiationObservable negotiationObservable; - @Inject private WebService webService; - @Inject private ContractNegotiationService contractNegotiationService; - @Inject private EndpointDataReferenceReceiverRegistry receiverRegistry; - @Inject private ManagementApiConfiguration apiConfig; - @Inject private TransferProcessService transferProcessService; - @Inject private TransactionContext transactionContext; - @Inject private CatalogService catalogService; - @Inject private ContractAgreementService agreementService; - @Inject private DataSourceRegistry dataSourceRegistry; - @Inject private Clock clock; - - @Override - public String name() { - return "Control Plane Adapter Extension"; - } - - @Override - public void initialize(ServiceExtensionContext context) { - ApiAdapterConfig config = new ApiAdapterConfig(context); - ListenerService listenerService = new ListenerService(); - - MessageBus messageBus = createMessageBus(listenerService, context, config); - ObjectStoreService storeService = getStoreService(context, config); - - ResultService resultService = new ResultService(config.getDefaultSyncRequestTimeout(), monitor); - ErrorResultService errorResultService = new ErrorResultService(monitor, messageBus); - - ContractNotificationSyncService contractSyncService = - new ContractSyncService(storeService, new LockMap()); - - DataTransferInitializer dataTransferInitializer = - new DataTransferInitializer(monitor, transferProcessService); - - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, - messageBus, - contractSyncService, - contractNegotiationService, - dataTransferInitializer); - - ContractNegotiationHandler contractNegotiationHandler = - getContractNegotiationHandler(monitor, contractNegotiationService, messageBus, config); - - DataRefNotificationSyncService dataRefSyncService = - new DataRefSyncService(storeService, new LockMap()); - DataReferenceHandler dataReferenceHandler = - new DataReferenceHandler(monitor, messageBus, dataRefSyncService); - - listenerService.addListener(Channel.INITIAL, contractNegotiationHandler); - listenerService.addListener(Channel.CONTRACT_CONFIRMATION, contractNotificationHandler); - listenerService.addListener(Channel.DATA_REFERENCE, dataReferenceHandler); - listenerService.addListener(Channel.RESULT, resultService); - listenerService.addListener(Channel.DLQ, errorResultService); - - initHttpController(messageBus, resultService, config); - initContractNegotiationListener( - negotiationObservable, messageBus, contractSyncService, dataTransferInitializer); - initDataReferenceReceiver(messageBus, dataRefSyncService); - initDataRefErrorHandler(messageBus, storeService, transferProcessService); - } - - private MessageBus createMessageBus( - ListenerService listenerService, ServiceExtensionContext context, ApiAdapterConfig config) { - if (!isPersistenceConfigured(config)) { - monitor.info( - "Persistent layer configuration is missing. Starting MessageBus in 'IN MEMORY' mode."); - return new InMemoryMessageBus( - monitor, listenerService, config.getInMemoryMessageBusThreadNumber()); - } - - SqlQueueStore sqlQueueStore = - new SqlQueueStore( - dataSourceRegistry, - config.getDataSourceName(), - transactionContext, - context.getTypeManager().getMapper(), - new PostgresDialectQueueStatements(), - context.getConnectorId(), - clock); - SqlMessageBus messageBus = - new SqlMessageBus( - monitor, - listenerService, - sqlQueueStore, - config.getSqlMessageBusThreadNumber(), - config.getSqlMessageBusMaxDelivery()); - initMessageBus(messageBus, config); - return messageBus; - } - - private ObjectStoreService getStoreService( - ServiceExtensionContext context, ApiAdapterConfig config) { - if (!isPersistenceConfigured(config)) { - monitor.info( - "Persistent layer configuration is missing. Starting Control Plane Adapter Extension in 'IN MEMORY' mode."); - return new ObjectStoreServiceInMemory(context.getTypeManager().getMapper()); - } - - ObjectMapper mapper = context.getTypeManager().getMapper(); - SqlObjectStore objectStore = - new SqlObjectStore( - dataSourceRegistry, - config.getDataSourceName(), - transactionContext, - mapper, - new PostgresDialectObjectStoreStatements()); - return new ObjectStoreServiceSql(mapper, objectStore); - } - - private void initMessageBus(SqlMessageBus messageBus, ApiAdapterConfig config) { - final int poolSize = 1; - final int initialDelay = 5; - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(poolSize); - scheduler.scheduleAtFixedRate( - () -> messageBus.deliverMessages(config.getSqlMessageBusMaxDelivery()), - initialDelay, - config.getSqlMessageBusDeliveryInterval(), - TimeUnit.SECONDS); - } - - private void initHttpController( - MessageBus messageBus, ResultService resultService, ApiAdapterConfig config) { - webService.registerResource( - apiConfig.getContextAlias(), - new HttpController(monitor, resultService, messageBus, config)); - } - - private ContractNegotiationHandler getContractNegotiationHandler( - Monitor monitor, - ContractNegotiationService contractNegotiationService, - MessageBus messageBus, - ApiAdapterConfig config) { - return new ContractNegotiationHandler( - monitor, - messageBus, - contractNegotiationService, - new CatalogCachedRetriever( - new CatalogRetriever(config.getCatalogRequestLimit(), catalogService), - new ExpiringMap<>()), - new ContractAgreementRetriever(monitor, agreementService)); - } - - private void initDataReferenceReceiver( - MessageBus messageBus, DataRefNotificationSyncService dataRefSyncService) { - EndpointDataReferenceReceiver dataReferenceReceiver = - new EndpointDataReferenceReceiverImpl(monitor, messageBus, dataRefSyncService); - receiverRegistry.registerReceiver(dataReferenceReceiver); - } - - private void initContractNegotiationListener( - ContractNegotiationObservable negotiationObservable, - MessageBus messageBus, - ContractNotificationSyncService contractSyncService, - DataTransferInitializer dataTransferInitializer) { - ContractNegotiationListener contractNegotiationListener = - new ContractNegotiationListenerImpl( - monitor, messageBus, contractSyncService, dataTransferInitializer); - if (nonNull(negotiationObservable)) { - negotiationObservable.registerListener(contractNegotiationListener); - } - } - - private void initDataRefErrorHandler( - MessageBus messageBus, - ObjectStoreService objectStore, - TransferProcessService transferProcessService) { - - final int poolSize = 1; - final int initialDelay = 5; - final int interval = 5; - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(poolSize); - - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, objectStore, transferProcessService); - - scheduler.scheduleAtFixedRate( - errorHandler::validateActiveProcesses, initialDelay, interval, TimeUnit.SECONDS); - } - - private boolean isPersistenceConfigured(ApiAdapterConfig config) { - return Objects.nonNull(config.getDataSourceName()) - && Objects.nonNull(config.getDataSourceUrl()); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/HttpController.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/HttpController.java deleted file mode 100644 index 111d57068..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/HttpController.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter; - -import static java.util.Objects.isNull; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.ws.rs.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.util.string.StringUtils; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.eclipse.tractusx.edc.cp.adapter.service.ResultService; - -@Consumes({MediaType.APPLICATION_JSON}) -@Produces({MediaType.APPLICATION_JSON}) -@Path("/adapter/asset") -@RequiredArgsConstructor -@Tag(name = "Control Plane Adapter") -public class HttpController { - private final Monitor monitor; - private final ResultService resultService; - private final MessageBus messageBus; - private final ApiAdapterConfig config; - - @GET - @Path("sync/{assetId}") - public Response getAssetSynchronous( - @PathParam("assetId") String assetId, - @QueryParam("providerUrl") String providerUrl, - @QueryParam("contractAgreementId") String contractAgreementId, - @QueryParam("contractAgreementReuse") @DefaultValue("true") boolean contractAgreementReuse, - @QueryParam("timeout") String timeout) { - - if (invalidParams(assetId, providerUrl)) { - return badRequestResponse(); - } - - String traceId = - initiateProcess(assetId, providerUrl, contractAgreementId, contractAgreementReuse); - - try { - ProcessData processData = - StringUtils.isNullOrEmpty(timeout) || !isNumeric(timeout) - ? resultService.pull(traceId) - : resultService.pull(traceId, Long.parseLong(timeout), TimeUnit.SECONDS); - - if (Objects.isNull(processData)) { - return notFoundResponse(); - } - if (Objects.nonNull(processData.getErrorStatus())) { - return errorResponse(processData); - } - if (Objects.nonNull(processData.getEndpointDataReference())) { - return okResponse(processData); - } - return timeoutResponse(); - } catch (InterruptedException e) { - monitor.severe("InterruptedException", e); - return notFoundResponse(); - } - } - - private Response badRequestResponse() { - return Response.status(Response.Status.BAD_REQUEST) - .entity("AssetId or providerUrl is empty!") - .build(); - } - - private boolean invalidParams(String assetId, String providerUrl) { - return isNull(assetId) || assetId.isBlank() || isNull(providerUrl) || providerUrl.isBlank(); - } - - private String initiateProcess( - String assetId, - String providerUrl, - String contractAgreementId, - boolean contractAgreementReuse) { - ProcessData processData = - ProcessData.builder() - .assetId(assetId) - .provider(providerUrl) - .contractAgreementId(contractAgreementId) - .contractAgreementReuseOn(isContractAgreementReuseOn(contractAgreementReuse)) - .catalogExpiryTime(config.getCatalogExpireAfterTime()) - .build(); - - Message message = - new DataReferenceRetrievalDto(processData, config.getDefaultMessageRetryNumber()); - messageBus.send(Channel.INITIAL, message); - return message.getTraceId(); - } - - private boolean isContractAgreementReuseOn(boolean contractAgreementReuse) { - return contractAgreementReuse && config.isContractAgreementReuseOn(); - } - - private Response notFoundResponse() { - return Response.status(Response.Status.NOT_FOUND) - .entity(Response.Status.NOT_FOUND.getReasonPhrase()) - .build(); - } - - private Response errorResponse(ProcessData processData) { - return Response.status(processData.getErrorStatus()) - .entity(processData.getErrorMessage()) - .build(); - } - - private Response okResponse(ProcessData processData) { - return Response.status(Response.Status.OK) - .entity(processData.getEndpointDataReference()) - .build(); - } - - private Response timeoutResponse() { - return Response.status(Response.Status.REQUEST_TIMEOUT) - .entity(Response.Status.REQUEST_TIMEOUT.getReasonPhrase()) - .build(); - } - - private boolean isNumeric(String str) { - return str != null && str.matches("[0-9]+"); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/DataReferenceRetrievalDto.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/DataReferenceRetrievalDto.java deleted file mode 100644 index 675a991e4..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/DataReferenceRetrievalDto.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.dto; - -import lombok.NoArgsConstructor; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; - -@NoArgsConstructor -public class DataReferenceRetrievalDto extends Message { - public DataReferenceRetrievalDto(ProcessData payload, int retryLimit) { - super(payload, retryLimit); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/ProcessData.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/ProcessData.java deleted file mode 100644 index 98792df2a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/dto/ProcessData.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.dto; - -import static java.lang.System.currentTimeMillis; - -import jakarta.ws.rs.core.Response; -import lombok.*; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; - -@Getter -@ToString -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class ProcessData { - private final long timestamp = currentTimeMillis(); - - // request data - private String assetId; - private String provider; - private String contractOfferId; - private int catalogExpiryTime; - private boolean contractAgreementReuseOn; - - // contract data - @Setter private String contractNegotiationId; - @Setter private String contractAgreementId; - @Builder.Default @Setter private boolean isContractConfirmed = false; - @Setter private String transferProcessId; - - // result/response data - @Setter private EndpointDataReference endpointDataReference; - @Setter private String errorMessage; - @Setter private Response.Status errorStatus; -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ConfigurationException.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ConfigurationException.java deleted file mode 100644 index bd8e2ad2a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ConfigurationException.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.exception; - -public class ConfigurationException extends RuntimeException { - public ConfigurationException(String message) { - super(message); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ExternalRequestException.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ExternalRequestException.java deleted file mode 100644 index f8bcb8939..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ExternalRequestException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.exception; - -public class ExternalRequestException extends RuntimeException { - public ExternalRequestException(String message) { - super(message); - } - - public ExternalRequestException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ResourceNotFoundException.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ResourceNotFoundException.java deleted file mode 100644 index bde662d42..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/exception/ResourceNotFoundException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.exception; - -public class ResourceNotFoundException extends RuntimeException { - public ResourceNotFoundException(String message) { - super(message); - } - - public ResourceNotFoundException(String message, Exception e) { - super(message, e); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Channel.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Channel.java deleted file mode 100644 index 170c6fc3d..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Channel.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -public enum Channel { - INITIAL, - CONTRACT_CONFIRMATION, - DATA_REFERENCE, - RESULT, - DLQ -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBus.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBus.java deleted file mode 100644 index 6b4043e1f..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBus.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import static java.util.Objects.isNull; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.eclipse.edc.spi.monitor.Monitor; - -public class InMemoryMessageBus implements MessageBus { - private final Monitor monitor; - private final ListenerService listenerService; - private final ScheduledExecutorService executorService; - - public InMemoryMessageBus(Monitor monitor, ListenerService listenerService, int threadPoolSize) { - this.monitor = monitor; - this.listenerService = listenerService; - executorService = Executors.newScheduledThreadPool(threadPoolSize); - } - - @Override - public void send(Channel name, Message message) { - if (isNull(message)) { - monitor.warning(String.format("Message is empty, channel: %s", name)); - } else { - monitor.info(String.format("[%s] Message sent to channel: %s", message.getTraceId(), name)); - executorService.submit(() -> run(name, message)); - } - } - - /** Returns 'false' if message processing should be retried. * */ - protected boolean run(Channel name, Message message) { - try { - listenerService.getListener(name).process(message); - message.clearErrors(); - return true; - } catch (Exception e) { - monitor.warning(String.format("[%s] Message processing error.", message.getTraceId()), e); - if (!message.canRetry()) { - monitor.warning(String.format("[%s] Message reached retry limit!", message.getTraceId())); - sendMessageToDlq(message, e); - return true; - } - long delayTime = message.unsucceeded(); - executorService.schedule(() -> send(name, message), delayTime, TimeUnit.MILLISECONDS); - return false; - } - } - - private void sendMessageToDlq(Message message, Exception finalException) { - message.clearErrors(); - message.setFinalException(finalException); - run(Channel.DLQ, message); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Listener.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Listener.java deleted file mode 100644 index 911ba8902..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Listener.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -public interface Listener

> { - void process(P message); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/ListenerService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/ListenerService.java deleted file mode 100644 index 3e79e3e6f..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/ListenerService.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import static java.util.Objects.isNull; - -import java.util.HashMap; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.eclipse.tractusx.edc.cp.adapter.exception.ConfigurationException; - -@RequiredArgsConstructor -public class ListenerService { - /** only single listener for a message at the moment * */ - private final Map listeners = new HashMap<>(); - - public void addListener(Channel name, Listener listener) { - listeners.put(name, listener); - } - - public void removeListener(Channel name) { - listeners.remove(name); - } - - Listener getListener(Channel name) { - Listener listener = listeners.get(name); - if (isNull(listener)) { - throw new ConfigurationException("No listener found for channel: " + name); - } - return listener; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Message.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Message.java deleted file mode 100644 index 9bb67c414..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/Message.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import java.util.UUID; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@NoArgsConstructor -@Getter -@Setter -public abstract class Message { - private String traceId; - private T payload; - private int errorNumber; - private int retryLimit; - private Exception exception; - private Exception finalException; - - public Message(String traceId, T payload, int retryLimit) { - this.traceId = traceId; - this.retryLimit = retryLimit; - this.payload = payload; - } - - public Message(T payload, int retryLimit) { - this.traceId = UUID.randomUUID().toString(); - this.retryLimit = retryLimit; - this.payload = payload; - } - - protected long unsucceeded() { - errorNumber++; - return getDelayTime(); - } - - protected void clearErrors() { - errorNumber = 0; - } - - protected boolean canRetry() { - return errorNumber < retryLimit; - } - - protected void setFinalException(Exception e) { - this.finalException = e; - } - - private int getDelayTime() { - return errorNumber < 5 ? errorNumber * 750 : (int) Math.pow(errorNumber, 2) * 150; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/MessageBus.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/MessageBus.java deleted file mode 100644 index b1021dd6d..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/MessageBus.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -public interface MessageBus { - void send(Channel name, Message message); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBus.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBus.java deleted file mode 100644 index d76079c53..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBus.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import static java.util.Objects.isNull; - -import java.time.Instant; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.store.SqlQueueStore; -import org.eclipse.tractusx.edc.cp.adapter.store.model.QueueMessage; - -public class SqlMessageBus implements MessageBus { - private final int maxDelivery; - private final Monitor monitor; - private final ListenerService listenerService; - private final ScheduledExecutorService executorService; - private final SqlQueueStore store; - - public SqlMessageBus( - Monitor monitor, - ListenerService listenerService, - SqlQueueStore sqlQueueStore, - int threadPoolSize, - int maxDelivery) { - this.monitor = monitor; - this.listenerService = listenerService; - this.store = sqlQueueStore; - this.maxDelivery = maxDelivery; - this.executorService = Executors.newScheduledThreadPool(threadPoolSize); - } - - @Override - public void send(Channel channel, Message message) { - if (isNull(message)) { - monitor.warning(String.format("Message is empty, channel: %s", channel)); - return; - } - monitor.info(String.format("[%s] Message sent to channel: %s", message.getTraceId(), channel)); - long now = Instant.now().toEpochMilli(); - store.saveMessage( - QueueMessage.builder().channel(channel.name()).message(message).invokeAfter(now).build()); - - deliverMessages(maxDelivery); - } - - public void deliverMessages(int maxElements) { - List queueMessages = store.findMessagesToSend(maxElements); - monitor.debug(String.format("Found [%d] messages to send.", queueMessages.size())); - queueMessages.forEach( - queueMessage -> executorService.submit(() -> deliverMessage(queueMessage))); - } - - private void deliverMessage(QueueMessage queueMessage) { - Channel channel = Channel.valueOf(queueMessage.getChannel()); - Message message = queueMessage.getMessage(); - - int currentErrorNumber = message.getErrorNumber(); - message.clearErrors(); - - try { - listenerService.getListener(channel).process(message); - store.deleteMessage(queueMessage.getId()); - monitor.debug(String.format("[%s] Message sent and removed.", queueMessage.getId())); - } catch (Exception e) { - monitor.warning(String.format("[%s] Message processing error.", message.getTraceId()), e); - message.setErrorNumber(currentErrorNumber); - if (!message.canRetry()) { - monitor.warning(String.format("[%s] Message reached retry limit!", message.getTraceId())); - sendMessageToDlq(message, e); - store.deleteMessage(queueMessage.getId()); - return; - } - long delayTime = message.unsucceeded(); - long now = Instant.now().toEpochMilli(); - queueMessage.setInvokeAfter(now + delayTime); - message.setException(e); - store.updateMessage(queueMessage); - } - } - - private void sendMessageToDlq(Message message, Exception finalException) { - message.clearErrors(); - message.setFinalException(finalException); - send(Channel.DLQ, message); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogCachedRetriever.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogCachedRetriever.java deleted file mode 100644 index b57d6c9bc..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogCachedRetriever.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.util.collection.CollectionUtil; -import org.eclipse.tractusx.edc.cp.adapter.util.ExpiringMap; - -@RequiredArgsConstructor -public class CatalogCachedRetriever { - private final CatalogRetriever catalogRetriever; - private final ExpiringMap catalogCache; - - public Catalog getEntireCatalog(String providerUrl, int catalogExpiryTime) { - return getEntireCatalog(providerUrl, null, catalogExpiryTime); - } - - public Catalog getEntireCatalog(String providerUrl, String assetId, int catalogExpiryTime) { - Catalog catalog = catalogCache.get(getKey(providerUrl, assetId), catalogExpiryTime); - if (Objects.nonNull(catalog)) { - return catalog; - } - - catalog = catalogRetriever.getEntireCatalog(providerUrl, assetId); - - if (Objects.nonNull(catalog) && CollectionUtil.isNotEmpty(catalog.getContractOffers())) { - catalogCache.put(getKey(providerUrl, assetId), catalog); - } - - return catalog; - } - - private String getKey(String providerUrl, String assetId) { - return providerUrl + "::" + assetId; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetriever.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetriever.java deleted file mode 100644 index 51ccef238..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetriever.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.spi.catalog.CatalogService; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.tractusx.edc.cp.adapter.exception.ExternalRequestException; - -@RequiredArgsConstructor -public class CatalogRetriever { - private final int limit; - private final CatalogService catalogService; - - public Catalog getEntireCatalog(String providerUrl) { - return getEntireCatalog(providerUrl, null); - } - - public Catalog getEntireCatalog(String providerUrl, String assetId) { - int offset = 0; - - Catalog catalogResult = getCatalog(providerUrl, getQuerySpec(limit, offset, assetId)); - boolean reachedLastPage = catalogResult.getContractOffers().size() < limit; - - while (!reachedLastPage) { - offset += limit; - Catalog catalog = getCatalog(providerUrl, getQuerySpec(limit, offset, assetId)); - catalogResult.getContractOffers().addAll(catalog.getContractOffers()); - reachedLastPage = catalog.getContractOffers().size() < limit; - } - - return catalogResult; - } - - public Catalog getCatalog(String providerUrl, QuerySpec querySpec) { - try { - return catalogService.getByProviderUrl(providerUrl, querySpec).get(); - } catch (InterruptedException | ExecutionException e) { - throw new ExternalRequestException("Could not retrieve contract offer.", e); - } - } - - private QuerySpec getQuerySpec(int limit, int offset, String assetId) { - List filters = - Objects.isNull(assetId) - ? Collections.emptyList() - : List.of(new Criterion("asset:prop:id", "=", assetId)); - return QuerySpec.Builder.newInstance().offset(offset).filter(filters).limit(limit).build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java deleted file mode 100644 index 7d87948b0..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetriever.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.service.spi.result.ServiceResult; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; - -@RequiredArgsConstructor -public class ContractAgreementRetriever { - private final Monitor monitor; - private final ContractAgreementService agreementService; - - public ContractAgreement getExistingContractById(String contractAgreementId) { - return agreementService.findById(contractAgreementId); - } - - public ContractAgreement getExistingContractByAssetId(String assetId) { - Collection agreements = getContractAgreementsByAssetId(assetId); - - validateResults(agreements); - - long now = Instant.now().getEpochSecond(); - return agreements.stream() - .filter(agreement -> agreement.getContractStartDate() < now) - .filter(agreement -> agreement.getContractEndDate() > now) - .findFirst() - .orElse(null); - } - - private Collection getContractAgreementsByAssetId(String assetId) { - QuerySpec querySpec = - QuerySpec.Builder.newInstance() - .filter(List.of(new Criterion("policy.target", "=", assetId))) - .limit(500) - .build(); - ServiceResult> result = agreementService.query(querySpec); - return result.succeeded() - ? result.getContent().collect(Collectors.toList()) - : Collections.emptyList(); - } - - private void validateResults(Collection agreements) { - if (agreements.size() > 1) { - monitor.warning( - "More than one agreement found for a given assetId! First of the list will be used!"); - } - int numberOfProviders = - agreements.stream() - .collect(Collectors.groupingBy(ContractAgreement::getProviderAgentId)) - .size(); - if (numberOfProviders > 1) { - monitor.warning("Contract agreement: given assetId found for more than one provider!"); - } - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java deleted file mode 100644 index b0d7bf237..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandler.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import jakarta.ws.rs.core.Response; -import java.time.Instant; -import java.util.*; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractOfferRequest; -import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.exception.ResourceNotFoundException; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; - -@RequiredArgsConstructor -public class ContractNegotiationHandler implements Listener { - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNegotiationService contractNegotiationService; - private final CatalogCachedRetriever catalogRetriever; - private final ContractAgreementRetriever agreementRetriever; - - @Override - public void process(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] RequestHandler: input request: [%s]", dto.getTraceId(), dto.getPayload())); - ProcessData processData = dto.getPayload(); - - ContractAgreement contractAgreement = getContractAgreementById(dto); - if (Objects.nonNull(dto.getPayload().getContractAgreementId()) && contractAgreement == null) { - sendNotFoundErrorResult(dto, getAgreementNotFoundMessage(dto)); - return; - } - - if (Objects.isNull(contractAgreement)) { - contractAgreement = - agreementRetriever.getExistingContractByAssetId(dto.getPayload().getAssetId()); - } - - if (Objects.nonNull(contractAgreement) && isContractValid(contractAgreement)) { - monitor.info( - String.format("[%s] existing ContractAgreement taken from EDC.", dto.getTraceId())); - dto.getPayload().setContractAgreementId(contractAgreement.getId()); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); - return; - } - - ContractOffer contractOffer = - findContractOffer( - processData.getAssetId(), - processData.getProvider(), - processData.getCatalogExpiryTime()); - - if (Objects.isNull(contractOffer)) { - sendNotFoundErrorResult(dto, getContractNotFoundMessage(dto)); - return; - } - - String contractNegotiationId = - initializeContractNegotiation( - contractOffer, dto.getPayload().getProvider(), dto.getTraceId()); - dto.getPayload().setContractNegotiationId(contractNegotiationId); - - messageBus.send(Channel.CONTRACT_CONFIRMATION, dto); - } - - private ContractAgreement getContractAgreementById(DataReferenceRetrievalDto dto) { - return Optional.ofNullable(dto.getPayload().getContractAgreementId()) - .map(agreementRetriever::getExistingContractById) - .orElse(null); - } - - private boolean isContractValid(ContractAgreement contractAgreement) { - long now = Instant.now().getEpochSecond(); - return Objects.nonNull(contractAgreement) - && contractAgreement.getContractStartDate() < now - && contractAgreement.getContractEndDate() > now; - } - - private ContractOffer findContractOffer( - String assetId, String providerUrl, int catalogExpiryTime) { - Catalog catalog = catalogRetriever.getEntireCatalog(providerUrl, assetId, catalogExpiryTime); - return Optional.ofNullable(catalog.getContractOffers()).orElse(Collections.emptyList()).stream() - .filter(it -> it.getAsset().getId().equals(assetId)) - .findFirst() - .orElse(null); - } - - private String initializeContractNegotiation( - ContractOffer contractOffer, String providerUrl, String traceId) { - monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - start", traceId)); - ContractOfferRequest contractOfferRequest = - ContractOfferRequest.Builder.newInstance() - .connectorAddress(providerUrl) - .contractOffer(contractOffer) - .type(ContractOfferRequest.Type.INITIAL) - .connectorId("provider") - .protocol("ids-multipart") - .correlationId(traceId) - .build(); - - ContractNegotiation contractNegotiation = - contractNegotiationService.initiateNegotiation(contractOfferRequest); - monitor.info(String.format("[%s] RequestHandler: initiateNegotiation - end", traceId)); - return Optional.ofNullable(contractNegotiation.getId()) - .orElseThrow(() -> new ResourceNotFoundException("Could not find Contract NegotiationId")); - } - - private void sendNotFoundErrorResult(DataReferenceRetrievalDto dto, String message) { - dto.getPayload().setErrorMessage(message); - dto.getPayload().setErrorStatus(Response.Status.NOT_FOUND); - messageBus.send(Channel.RESULT, dto); - } - - private String getAgreementNotFoundMessage(DataReferenceRetrievalDto dto) { - return "Not found the contract agreement with ID: " + dto.getPayload().getContractAgreementId(); - } - - private String getContractNotFoundMessage(DataReferenceRetrievalDto dto) { - return "Could not find Contract Offer for given Asset Id"; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractInfo.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractInfo.java deleted file mode 100644 index e4437cbbb..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class ContractInfo { - @Getter private String contractAgreementId; - private ContractState contractState; - - public ContractInfo(String contractAgreementId, ContractState contractState) { - this.contractAgreementId = contractAgreementId; - this.contractState = contractState; - } - - public ContractInfo(ContractState contractState) { - this.contractState = contractState; - } - - public boolean isConfirmed() { - return ContractState.CONFIRMED.equals(contractState); - } - - public boolean isDeclined() { - return ContractState.DECLINED.equals(contractState); - } - - public boolean isError() { - return ContractState.ERROR.equals(contractState); - } - - protected enum ContractState { - CONFIRMED, - DECLINED, - ERROR; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java deleted file mode 100644 index a441eddf6..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import static java.util.Objects.isNull; - -import jakarta.ws.rs.core.Response; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; - -@RequiredArgsConstructor -public class ContractNegotiationListenerImpl implements ContractNegotiationListener { - public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; - public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNotificationSyncService syncService; - private final DataTransferInitializer dataTransfer; - - @Override - public void confirmed(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractConfirmation event"); - String negotiationId = negotiation.getId(); - String agreementId = negotiation.getContractAgreement().getId(); - DataReferenceRetrievalDto dto = - syncService.exchangeConfirmedContract(negotiationId, agreementId); - if (isNull(dto)) { - return; - } - dto.getPayload().setContractAgreementId(agreementId); - initiateDataTransfer(dto); - syncService.removeDto(negotiationId); - } - - @Override - public void declined(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractDeclined event"); - String contractNegotiationId = negotiation.getId(); - DataReferenceRetrievalDto dto = syncService.exchangeDeclinedContract(contractNegotiationId); - if (isNull(dto)) { - return; - } - sendErrorResult(dto, CONTRACT_DECLINED_MESSAGE); - syncService.removeDto(contractNegotiationId); - } - - @Override - public void failed(ContractNegotiation negotiation) { - monitor.info("ContractConfirmationHandler: received ContractError event"); - String contractNegotiationId = negotiation.getId(); - DataReferenceRetrievalDto dto = syncService.exchangeErrorContract(contractNegotiationId); - if (isNull(dto)) { - return; - } - sendErrorResult(dto, CONTRACT_ERROR_MESSAGE); - syncService.removeDto(contractNegotiationId); - } - - public void initiateDataTransfer(DataReferenceRetrievalDto dto) { - String transferProcessId = dataTransfer.initiate(dto); - dto.getPayload().setTransferProcessId(transferProcessId); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.DATA_REFERENCE, dto); - } - - private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { - dto.getPayload().setErrorMessage(errorMessage); - dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); - messageBus.send(Channel.RESULT, dto); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java deleted file mode 100644 index 5bd254194..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandler.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import static jakarta.ws.rs.core.Response.Status; -import static java.util.Objects.isNull; - -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; -import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; - -@RequiredArgsConstructor -public class ContractNotificationHandler implements Listener { - public static final String CONTRACT_DECLINED_MESSAGE = "Contract for asset is declined."; - public static final String CONTRACT_ERROR_MESSAGE = "Contract Error for asset."; - private final Monitor monitor; - private final MessageBus messageBus; - private final ContractNotificationSyncService syncService; - private final ContractNegotiationService contractNegotiationService; - private final DataTransferInitializer dataTransfer; - - @Override - public void process(DataReferenceRetrievalDto dto) { - monitor.info( - String.format("[%s] ContractConfirmationHandler: received message.", dto.getTraceId())); - String contractNegotiationId = dto.getPayload().getContractNegotiationId(); - - if (dto.getPayload().isContractConfirmed()) { - initiateDataTransfer(dto); - return; - } - - ContractNegotiation contractNegotiation = - contractNegotiationService.findbyId(contractNegotiationId); - if (isContractConfirmed(contractNegotiation)) { - dto.getPayload().setContractAgreementId(contractNegotiation.getContractAgreement().getId()); - initiateDataTransfer(dto); - return; - } - - ContractInfo contractInfo = syncService.exchangeDto(dto); - if (isNull(contractInfo)) { - return; - } - - if (contractInfo.isConfirmed()) { - dto.getPayload().setContractAgreementId(contractInfo.getContractAgreementId()); - initiateDataTransfer(dto); - } else { - sendErrorResult( - dto, contractInfo.isDeclined() ? CONTRACT_DECLINED_MESSAGE : CONTRACT_ERROR_MESSAGE); - } - syncService.removeContractInfo(contractNegotiationId); - } - - public void initiateDataTransfer(DataReferenceRetrievalDto dto) { - String transferProcessId = dataTransfer.initiate(dto); - dto.getPayload().setTransferProcessId(transferProcessId); - dto.getPayload().setContractConfirmed(true); - messageBus.send(Channel.DATA_REFERENCE, dto); - } - - private void sendErrorResult(DataReferenceRetrievalDto dto, String errorMessage) { - dto.getPayload().setErrorMessage(errorMessage); - dto.getPayload().setErrorStatus(Status.BAD_GATEWAY); - messageBus.send(Channel.RESULT, dto); - } - - private boolean isContractConfirmed(ContractNegotiation contractNegotiation) { - return Objects.nonNull(contractNegotiation) - && contractNegotiation.getState() == ContractNegotiationStates.CONFIRMED.code(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationSyncService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationSyncService.java deleted file mode 100644 index e687822c2..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationSyncService.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; - -public interface ContractNotificationSyncService { - DataReferenceRetrievalDto exchangeConfirmedContract( - String contractNegotiationId, String contractAgreementId); - - DataReferenceRetrievalDto exchangeDeclinedContract(String contractNegotiationId); - - DataReferenceRetrievalDto exchangeErrorContract(String contractNegotiationId); - - ContractInfo exchangeDto(DataReferenceRetrievalDto dto); - - void removeContractInfo(String key); - - void removeDto(String key); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncService.java deleted file mode 100644 index 44f3cbce6..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncService.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import static java.util.Objects.isNull; - -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectType; -import org.eclipse.tractusx.edc.cp.adapter.util.LockMap; - -public class ContractSyncService implements ContractNotificationSyncService { - private final ObjectStoreService storeService; - private final LockMap locks; - - public ContractSyncService(ObjectStoreService storeService, LockMap locks) { - this.storeService = storeService; - this.locks = locks; - } - - @Override - public DataReferenceRetrievalDto exchangeConfirmedContract( - String negotiationId, String agreementId) { - locks.lock(negotiationId); - - DataReferenceRetrievalDto dto = - storeService.get(negotiationId, ObjectType.DTO, DataReferenceRetrievalDto.class); - - if (isNull(dto)) { - ContractInfo contractInfo = - new ContractInfo(agreementId, ContractInfo.ContractState.CONFIRMED); - storeService.put(negotiationId, ObjectType.CONTRACT_INFO, contractInfo); - } - locks.unlock(negotiationId); - return dto; - } - - @Override - public DataReferenceRetrievalDto exchangeDeclinedContract(String negotiationId) { - locks.lock(negotiationId); - - DataReferenceRetrievalDto dto = - storeService.get(negotiationId, ObjectType.DTO, DataReferenceRetrievalDto.class); - - if (isNull(dto)) { - ContractInfo contractInfo = new ContractInfo(ContractInfo.ContractState.DECLINED); - storeService.put(negotiationId, ObjectType.CONTRACT_INFO, contractInfo); - } - locks.unlock(negotiationId); - return dto; - } - - @Override - public DataReferenceRetrievalDto exchangeErrorContract(String negotiationId) { - locks.lock(negotiationId); - - DataReferenceRetrievalDto dto = - storeService.get(negotiationId, ObjectType.DTO, DataReferenceRetrievalDto.class); - - if (isNull(dto)) { - ContractInfo contractInfo = new ContractInfo(ContractInfo.ContractState.ERROR); - storeService.put(negotiationId, ObjectType.CONTRACT_INFO, contractInfo); - } - - locks.unlock(negotiationId); - return dto; - } - - @Override - public ContractInfo exchangeDto(DataReferenceRetrievalDto dto) { - String negotiationId = dto.getPayload().getContractNegotiationId(); - locks.lock(negotiationId); - - ContractInfo contractInfo = - storeService.get(negotiationId, ObjectType.CONTRACT_INFO, ContractInfo.class); - - if (isNull(contractInfo)) { - storeService.put(negotiationId, ObjectType.DTO, dto); - } - - locks.unlock(negotiationId); - return contractInfo; - } - - @Override - public void removeContractInfo(String negotiationId) { - storeService.remove(negotiationId, ObjectType.CONTRACT_INFO); - locks.removeLock(negotiationId); - } - - @Override - public void removeDto(String negotiationId) { - storeService.remove(negotiationId, ObjectType.DTO); - locks.removeLock(negotiationId); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java deleted file mode 100644 index 34812567f..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/DataTransferInitializer.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.transfer.spi.types.DataRequest; -import org.eclipse.edc.connector.transfer.spi.types.TransferType; -import org.eclipse.edc.service.spi.result.ServiceResult; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.exception.ExternalRequestException; - -@RequiredArgsConstructor -public class DataTransferInitializer { - private final Monitor monitor; - private final TransferProcessService transferProcessService; - - public String initiate(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] ContractConfirmationHandler: transfer init - start.", dto.getTraceId())); - DataAddress dataDestination = DataAddress.Builder.newInstance().type("HttpProxy").build(); - - TransferType transferType = - TransferType.Builder.transferType() - .contentType("application/octet-stream") - .isFinite(true) - .build(); - - DataRequest dataRequest = - DataRequest.Builder.newInstance() - .id(dto.getTraceId()) - .assetId(dto.getPayload().getAssetId()) - .contractId(dto.getPayload().getContractAgreementId()) - .connectorId("provider") - .connectorAddress(dto.getPayload().getProvider()) - .protocol("ids-multipart") - .dataDestination(dataDestination) - .managedResources(false) - .transferType(transferType) - .build(); - - ServiceResult result = transferProcessService.initiateTransfer(dataRequest); - monitor.info( - String.format("[%s] ContractConfirmationHandler: transfer init - end", dto.getTraceId())); - if (result.failed()) { - throwDataRefRequestException(dto); - } - - return result.getContent(); - } - - private void throwDataRefRequestException(DataReferenceRetrievalDto dto) { - throw new ExternalRequestException( - String.format( - "Data reference initial request failed! AssetId: %s", dto.getPayload().getAssetId())); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefNotificationSyncService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefNotificationSyncService.java deleted file mode 100644 index 2bb24af30..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefNotificationSyncService.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; - -public interface DataRefNotificationSyncService { - EndpointDataReference exchangeDto(DataReferenceRetrievalDto dto, String contractAgreementId); - - DataReferenceRetrievalDto exchangeDataReference( - EndpointDataReference dataReference, String contractAgreementId); - - void removeDataReference(String contractAgreementId); - - void removeDto(String key); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncService.java deleted file mode 100644 index 812ba70da..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncService.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static java.util.Objects.isNull; - -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectType; -import org.eclipse.tractusx.edc.cp.adapter.util.LockMap; - -@RequiredArgsConstructor -public class DataRefSyncService implements DataRefNotificationSyncService { - private final ObjectStoreService storeService; - private final LockMap locks; - - public EndpointDataReference exchangeDto(DataReferenceRetrievalDto dto, String agreementId) { - locks.lock(agreementId); - - EndpointDataReference dataReference = - storeService.get(agreementId, ObjectType.DATA_REFERENCE, EndpointDataReference.class); - - if (isNull(dataReference)) { - storeService.put(agreementId, ObjectType.DTO, dto); - } - locks.unlock(agreementId); - return dataReference; - } - - @Override - public DataReferenceRetrievalDto exchangeDataReference( - EndpointDataReference dataReference, String agreementId) { - locks.lock(agreementId); - - DataReferenceRetrievalDto dto = - storeService.get(agreementId, ObjectType.DTO, DataReferenceRetrievalDto.class); - - if (isNull(dto)) { - storeService.put(agreementId, ObjectType.DATA_REFERENCE, dataReference); - } - locks.unlock(agreementId); - return dto; - } - - @Override - public void removeDataReference(String agreementId) { - storeService.remove(agreementId, ObjectType.DATA_REFERENCE); - locks.removeLock(agreementId); - } - - @Override - public void removeDto(String agreementId) { - storeService.remove(agreementId, ObjectType.DTO); - locks.removeLock(agreementId); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java deleted file mode 100644 index 39885837c..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import jakarta.ws.rs.core.Response; -import java.util.List; -import lombok.AllArgsConstructor; -import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.util.string.StringUtils; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectType; - -@AllArgsConstructor -public class DataReferenceErrorHandler { - private static final String ERROR_MESSAGE = "Data reference process stage failed with status: "; - private final Monitor monitor; - private final MessageBus messageBus; - private final ObjectStoreService objectStore; - private final TransferProcessService transferProcessService; - - private final List errorStates = List.of("CANCELLED", "ERROR"); - - public void validateActiveProcesses() { - monitor.debug("Data reference error handling - START"); - objectStore.get(ObjectType.DTO, DataReferenceRetrievalDto.class).stream() - .filter(dto -> !StringUtils.isNullOrEmpty(dto.getPayload().getTransferProcessId())) - .forEach(this::validateProcess); - } - - private void validateProcess(DataReferenceRetrievalDto dto) { - String state = transferProcessService.getState(dto.getPayload().getTransferProcessId()); - if (errorStates.contains(state)) { - monitor.warning(String.format("[%s] ", dto.getTraceId()) + ERROR_MESSAGE + state); - String contractAgreementId = dto.getPayload().getContractAgreementId(); - objectStore.remove(contractAgreementId, ObjectType.DTO); - - dto.getPayload().setErrorStatus(Response.Status.BAD_GATEWAY); - dto.getPayload().setErrorMessage(ERROR_MESSAGE + state); - messageBus.send(Channel.RESULT, dto); - } - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandler.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandler.java deleted file mode 100644 index 2e5f901d3..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandler.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static java.util.Objects.isNull; - -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; - -@RequiredArgsConstructor -public class DataReferenceHandler implements Listener { - private final Monitor monitor; - private final MessageBus messageBus; - private final DataRefNotificationSyncService syncService; - - @Override - public void process(DataReferenceRetrievalDto dto) { - String contractAgreementId = dto.getPayload().getContractAgreementId(); - monitor.info(String.format("[%s] DataReference message received.", dto.getTraceId())); - - EndpointDataReference dataReference = syncService.exchangeDto(dto, contractAgreementId); - if (isNull(dataReference)) { - return; - } - - dto.getPayload().setEndpointDataReference(dataReference); - messageBus.send(Channel.RESULT, dto); - syncService.removeDataReference(contractAgreementId); - monitor.info(String.format("[%s] DataReference message processed.", dto.getTraceId())); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverImpl.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverImpl.java deleted file mode 100644 index 0f1173cf4..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static java.util.Objects.isNull; - -import java.util.concurrent.CompletableFuture; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.connector.transfer.spi.edr.EndpointDataReferenceReceiver; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.jetbrains.annotations.NotNull; - -@RequiredArgsConstructor -public class EndpointDataReferenceReceiverImpl implements EndpointDataReferenceReceiver { - private final Monitor monitor; - private final MessageBus messageBus; - private final DataRefNotificationSyncService syncService; - - @Override - public CompletableFuture> send(@NotNull EndpointDataReference dataReference) { - String contractAgreementId = dataReference.getProperties().get("cid"); - monitor.info(String.format("DataReference received, contractAgr.: %s", contractAgreementId)); - - DataReferenceRetrievalDto dto = - syncService.exchangeDataReference(dataReference, contractAgreementId); - if (isNull(dto)) { - return CompletableFuture.completedFuture(Result.success()); - } - dto.getPayload().setEndpointDataReference(dataReference); - messageBus.send(Channel.RESULT, dto); - syncService.removeDto(contractAgreementId); - - monitor.info(String.format("[%s] DataReference processed.", dto.getTraceId())); - return CompletableFuture.completedFuture(Result.success()); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ErrorResultService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ErrorResultService.java deleted file mode 100644 index 5428317e3..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ErrorResultService.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service; - -import jakarta.ws.rs.core.Response; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.exception.ExternalRequestException; -import org.eclipse.tractusx.edc.cp.adapter.exception.ResourceNotFoundException; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; - -@RequiredArgsConstructor -public class ErrorResultService implements Listener { - private static final Map, Response.Status> statusOfException = new HashMap<>(); - - static { - statusOfException.put(ExternalRequestException.class, Response.Status.BAD_GATEWAY); - statusOfException.put(ResourceNotFoundException.class, Response.Status.NOT_FOUND); - } - - private final Monitor monitor; - private final MessageBus messageBus; - - @Override - public void process(DataReferenceRetrievalDto dto) { - dto.getPayload().setErrorMessage(getErrorMessage(dto)); - dto.getPayload() - .setErrorStatus( - statusOfException.getOrDefault( - dto.getFinalException().getClass(), Response.Status.INTERNAL_SERVER_ERROR)); - log(dto); - messageBus.send(Channel.RESULT, dto); - } - - private String getErrorMessage(DataReferenceRetrievalDto dto) { - return Objects.nonNull(dto.getFinalException()) - ? dto.getFinalException().getMessage() - : "Unrecognized Exception."; - } - - private void log(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] Sending ERROR message to RESULT channel: %s / %s ", - dto.getTraceId(), - dto.getPayload().getErrorMessage(), - dto.getPayload().getErrorStatus())); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultService.java deleted file mode 100644 index 494237abe..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultService.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service; - -import static java.util.Objects.isNull; -import static java.util.concurrent.TimeUnit.SECONDS; - -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Listener; - -@RequiredArgsConstructor -public class ResultService implements Listener { - private final int CAPACITY = 1; - private final int DEFAULT_TIMEOUT; - private final Map> results = new ConcurrentHashMap<>(); - private final Monitor monitor; - - public ProcessData pull(String id) throws InterruptedException { - return pull(id, DEFAULT_TIMEOUT, SECONDS); - } - - public ProcessData pull(String id, long timeout, TimeUnit unit) throws InterruptedException { - if (!results.containsKey(id)) { - initiate(id); - } - ProcessData result = results.get(id).poll(timeout, unit); - results.remove(id); - return result; - } - - @Override - public void process(DataReferenceRetrievalDto dto) { - if (isNull(dto) || isNull(dto.getPayload())) { - throw new IllegalArgumentException(); - } - logReceivedResult(dto); - add(dto.getTraceId(), dto.getPayload()); - } - - private void add(String id, ProcessData processData) { - if (!results.containsKey(id)) { - initiate(id); - } - try { - results.get(id).add(processData); - } catch (IllegalStateException e) { - logIgnoredResult(id, processData); - } - } - - private void initiate(String id) { - results.put(id, new ArrayBlockingQueue<>(CAPACITY)); - } - - private void logReceivedResult(DataReferenceRetrievalDto dto) { - monitor.info( - String.format( - "[%s] Result received: %s", dto.getTraceId(), getResultInfo(dto.getPayload()))); - } - - private void logIgnoredResult(String id, ProcessData processData) { - monitor.warning( - String.format( - "[%s] Other Result was already returned! Result '%s' will be ignored!", - id, getResultInfo(processData))); - } - - private String getResultInfo(ProcessData processData) { - return Objects.nonNull(processData.getErrorMessage()) - ? processData.getErrorMessage() - : processData.getEndpointDataReference().getId(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreService.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreService.java deleted file mode 100644 index 6c3818379..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreService.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service.objectstore; - -import java.util.List; - -public interface ObjectStoreService { - void put(String key, ObjectType objectType, Object object); - - T get(String key, ObjectType objectType, Class type); - - void remove(String key, ObjectType objectType); - - List get(ObjectType objectType, Class type); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java deleted file mode 100644 index ccfd6e44d..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceInMemory.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service.objectstore; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import lombok.AllArgsConstructor; -import org.eclipse.edc.spi.EdcException; - -@AllArgsConstructor -public class ObjectStoreServiceInMemory implements ObjectStoreService { - private final ObjectMapper mapper; - private final Map map = new HashMap<>(); - - @Override - public void put(String key, ObjectType objectType, Object object) { - try { - map.put(getKey(key, objectType), mapper.writeValueAsString(object)); - } catch (JsonProcessingException e) { - - throw new IllegalArgumentException(); - } - } - - @Override - public T get(String key, ObjectType objectType, Class type) { - String json = map.get(getKey(key, objectType)); - return Objects.isNull(json) ? null : map(type, json); - } - - @Override - public List get(ObjectType objectType, Class type) { - return map.entrySet().stream() - .filter(entry -> entry.getKey().startsWith(objectType.name())) - .map(Map.Entry::getValue) - .map(s -> map(type, s)) - .collect(Collectors.toList()); - } - - @Override - public void remove(String key, ObjectType objectType) { - map.remove(getKey(key, objectType)); - } - - private String getKey(String key, ObjectType objectType) { - return objectType.name() + key; - } - - private T map(Class type, String json) { - T object = null; - try { - object = mapper.readValue(json, type); - } catch (JsonProcessingException e) { - throw new EdcException(e); - } - return object; - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java deleted file mode 100644 index 1fb528e2c..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectStoreServiceSql.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service.objectstore; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; -import lombok.AllArgsConstructor; -import org.eclipse.edc.util.collection.CollectionUtil; -import org.eclipse.tractusx.edc.cp.adapter.store.SqlObjectStore; -import org.eclipse.tractusx.edc.cp.adapter.store.model.ObjectEntity; - -@AllArgsConstructor -public class ObjectStoreServiceSql implements ObjectStoreService { - private final ObjectMapper mapper; - private final SqlObjectStore objectStore; - - @Override - public void put(String key, ObjectType objectType, Object object) { - ObjectEntity entity = - ObjectEntity.builder() - .id(key) - .type(objectType.name()) - .object(objectToJson(object, objectType.name())) - .build(); - objectStore.saveMessage(entity); - } - - @Override - public T get(String key, ObjectType objectType, Class type) { - ObjectEntity entity = objectStore.find(key, objectType.name()); - if (Objects.isNull(entity)) { - return null; - } - return jsonToObject(entity, type); - } - - @Override - public List get(ObjectType objectType, Class type) { - List entities = objectStore.find(objectType.name()); - if (CollectionUtil.isEmpty(entities)) { - return List.of(); - } - return entities.stream().map(entity -> jsonToObject(entity, type)).collect(Collectors.toList()); - } - - @Override - public void remove(String key, ObjectType objectType) { - objectStore.deleteMessage(key, objectType.name()); - } - - private String objectToJson(Object object, String type) { - if (Objects.isNull(object)) { - return null; - } - try { - return mapper.writeValueAsString(object); - } catch (JsonProcessingException e) { - - throw new IllegalArgumentException(String.format("Can not parse object of type %s", type)); - } - } - - private T jsonToObject(ObjectEntity entity, Class type) { - try { - return mapper.readValue(entity.getObject(), type); - } catch (JsonProcessingException e) { - - throw new IllegalArgumentException(String.format("Can not parse object of type %s", type)); - } - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectType.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectType.java deleted file mode 100644 index 5cbee2d0f..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/service/objectstore/ObjectType.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service.objectstore; - -public enum ObjectType { - DTO, - CONTRACT_INFO, - DATA_REFERENCE, - RESULT -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java deleted file mode 100644 index 5540428f3..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlObjectStore.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store; - -import static org.eclipse.edc.sql.SqlQueryExecutor.executeQuery; -import static org.eclipse.edc.sql.SqlQueryExecutor.executeQuerySingle; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Instant; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.edc.spi.persistence.EdcPersistenceException; -import org.eclipse.edc.sql.store.AbstractSqlStore; -import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; -import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.tractusx.edc.cp.adapter.store.model.ObjectEntity; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.ObjectStoreStatements; - -public class SqlObjectStore extends AbstractSqlStore { - private final ObjectStoreStatements statements; - - public SqlObjectStore( - DataSourceRegistry dataSourceRegistry, - String dataSourceName, - TransactionContext transactionContext, - ObjectMapper objectMapper, - ObjectStoreStatements statements) { - super(dataSourceRegistry, dataSourceName, transactionContext, objectMapper); - this.statements = statements; - } - - public void saveMessage(ObjectEntity objectEntity) { - long now = Instant.now().toEpochMilli(); - transactionContext.execute( - () -> { - try (var conn = getConnection()) { - var template = statements.getSaveObjectTemplate(); - executeQuery( - conn, - template, - objectEntity.getId(), - now, - objectEntity.getType(), - objectEntity.getObject()); - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - public ObjectEntity find(String id, String type) { - return transactionContext.execute( - () -> { - try (var connection = getConnection()) { - var sql = statements.getFindByIdAndTypeTemplate(); - return executeQuerySingle(connection, false, this::mapObjectEntity, sql, id, type); - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - public List find(String type) { - return transactionContext.execute( - () -> { - try (var connection = getConnection()) { - var sql = statements.getFindByTypeTemplate(); - Stream stream = - executeQuery(connection, false, this::mapObjectEntity, sql, type); - List result = stream.collect(Collectors.toList()); - stream.close(); - return result; - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - public void deleteMessage(String id, String type) { - transactionContext.execute( - () -> { - try (var connection = getConnection()) { - var stmt = statements.getDeleteTemplate(); - executeQuery(connection, stmt, id, type); - } catch (SQLException | IllegalStateException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - private ObjectEntity mapObjectEntity(ResultSet resultSet) throws SQLException { - return ObjectEntity.builder() - .id(resultSet.getString(statements.getIdColumn())) - .createdAt(resultSet.getLong(statements.getCreatedAtColumn())) - .type(resultSet.getString(statements.getTypeColumn())) - .object(resultSet.getString(statements.getObjectColumn())) - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java deleted file mode 100644 index 60336eb5b..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/SqlQueueStore.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store; - -import static org.eclipse.edc.sql.SqlQueryExecutor.executeQuery; -import static org.eclipse.edc.sql.SqlQueryExecutor.executeQuerySingle; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Clock; -import java.time.Instant; -import java.util.List; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.edc.spi.persistence.EdcPersistenceException; -import org.eclipse.edc.sql.lease.SqlLeaseContextBuilder; -import org.eclipse.edc.sql.store.AbstractSqlStore; -import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; -import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.store.model.QueueMessage; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.QueueStatements; - -public class SqlQueueStore extends AbstractSqlStore { - private final QueueStatements statements; - private final SqlLeaseContextBuilder leaseContext; - - public SqlQueueStore( - DataSourceRegistry dataSourceRegistry, - String dataSourceName, - TransactionContext transactionContext, - ObjectMapper objectMapper, - QueueStatements statements, - String connectorId, - Clock clock) { - super(dataSourceRegistry, dataSourceName, transactionContext, objectMapper); - this.statements = statements; - leaseContext = SqlLeaseContextBuilder.with(transactionContext, connectorId, statements, clock); - } - - public void saveMessage(QueueMessage queueMessage) { - long now = Instant.now().toEpochMilli(); - transactionContext.execute( - () -> { - try (var conn = getConnection()) { - var template = statements.getSaveMessageTemplate(); - executeQuery( - conn, - template, - now, - UUID.randomUUID().toString(), - queueMessage.getChannel(), - toJson(queueMessage.getMessage()), - queueMessage.getInvokeAfter()); - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - public QueueMessage findById(String id) { - return transactionContext.execute( - () -> { - try (var connection = getConnection()) { - var sql = statements.getFindByIdTemplate(); - return executeQuerySingle(connection, false, this::mapQueueMessage, sql, id); - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - public void deleteMessage(String id) { - transactionContext.execute( - () -> { - var existing = findById(id); - - if (existing != null) { - try (var connection = getConnection()) { - breakLease(connection, id); - var stmt = statements.getDeleteTemplate(); - executeQuery(connection, stmt, id); - } catch (SQLException | IllegalStateException e) { - - throw new EdcPersistenceException(e); - } - } - }); - } - - public void updateMessage(QueueMessage queueMessage) { - transactionContext.execute( - () -> { - var existing = findById(queueMessage.getId()); - - if (existing != null) { - try (var connection = getConnection()) { - var stmt = statements.getUpdateTemplate(); - breakLease(connection, queueMessage.getId()); - executeQuery( - connection, - stmt, - queueMessage.getChannel(), - toJson(queueMessage.getMessage()), - queueMessage.getInvokeAfter(), - queueMessage.getId()); - } catch (SQLException | IllegalStateException e) { - - throw new EdcPersistenceException(e); - } - } - }); - } - - public List findMessagesToSend(int max) { - long now = Instant.now().toEpochMilli(); - return transactionContext.execute( - () -> { - try (var connection = getConnection()) { - var sql = statements.getMessagesToSendTemplate(); - Stream stream = - executeQuery(connection, false, this::mapQueueMessage, sql, now, max); - List result = - stream - .map(queueMessage -> getLeasedQueueMessage(connection, queueMessage)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - stream.close(); - return result; - } catch (SQLException e) { - - throw new EdcPersistenceException(e); - } - }); - } - - private QueueMessage getLeasedQueueMessage(Connection connection, QueueMessage queueMessage) { - try { - acquireLease(connection, queueMessage.getId()); - return queueMessage; - } catch (IllegalStateException e) { - return null; - } - } - - private void acquireLease(Connection connection, String id) { - leaseContext.withConnection(connection).acquireLease(id); - } - - private void breakLease(Connection connection, String id) { - leaseContext.withConnection(connection).breakLease(id); - } - - private QueueMessage mapQueueMessage(ResultSet resultSet) throws SQLException { - return QueueMessage.builder() - .id(resultSet.getString(statements.getIdColumn())) - .message( - fromJson( - resultSet.getString(statements.getMessageColumn()), - DataReferenceRetrievalDto.class)) - .invokeAfter(resultSet.getLong(statements.getInvokeAfterColumn())) - .createdAt(resultSet.getLong(statements.getCreatedAtColumn())) - .channel(resultSet.getString(statements.getChannelColumn())) - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/ObjectEntity.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/ObjectEntity.java deleted file mode 100644 index ffefe3927..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/ObjectEntity.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.model; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Builder -public class ObjectEntity { - private String id; - private long createdAt; - private String type; - private String object; -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/QueueMessage.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/QueueMessage.java deleted file mode 100644 index e3d1ec8bb..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/model/QueueMessage.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.model; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; - -@Getter -@Setter -@Builder -public class QueueMessage { - private String id; - private long createdAt; - private String channel; - private Message message; - private long invokeAfter; -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectObjectStoreStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectObjectStoreStatements.java deleted file mode 100644 index caa7fb39a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectObjectStoreStatements.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema; - -import static java.lang.String.format; - -import org.eclipse.edc.sql.dialect.BaseSqlDialect; - -public class BaseSqlDialectObjectStoreStatements implements ObjectStoreStatements { - @Override - public String getSaveObjectTemplate() { - return format( - "INSERT INTO %s (%s, %s, %s, %s) VALUES(?, ?, ?, ?%s)", - getObjectStoreTable(), - getIdColumn(), - getCreatedAtColumn(), - getTypeColumn(), - getObjectColumn(), - getFormatJsonOperator()); - } - - @Override - public String getFindByIdAndTypeTemplate() { - return format( - "SELECT * FROM %s WHERE %s = ? AND %s = ?", - getObjectStoreTable(), getIdColumn(), getTypeColumn()); - } - - @Override - public String getFindByTypeTemplate() { - return format("SELECT * FROM %s WHERE %s = ?", getObjectStoreTable(), getTypeColumn()); - } - - @Override - public String getDeleteTemplate() { - return format( - "DELETE FROM %s WHERE %s = ? AND %s = ?;", - getObjectStoreTable(), getIdColumn(), getTypeColumn()); - } - - protected String getFormatJsonOperator() { - return BaseSqlDialect.getJsonCastOperator(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectQueueStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectQueueStatements.java deleted file mode 100644 index 7bf19422c..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/BaseSqlDialectQueueStatements.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema; - -import static java.lang.String.format; - -import org.eclipse.edc.sql.dialect.BaseSqlDialect; - -public class BaseSqlDialectQueueStatements implements QueueStatements { - - @Override - public String getSaveMessageTemplate() { - return format( - "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES(?, ?, ?, ?%s, ?)", - getQueueTable(), - getCreatedAtColumn(), - getIdColumn(), - getChannelColumn(), - getMessageColumn(), - getInvokeAfterColumn(), - getFormatJsonOperator()); - } - - @Override - public String getAllMessagesTemplate() { - return format("SELECT * FROM %s ", getQueueTable()); - } - - @Override - public String getMessagesToSendTemplate() { - return format( - "SELECT * FROM %s WHERE %s <= ? AND %s IS NULL LIMIT ?", - getQueueTable(), getInvokeAfterColumn(), getLeaseIdColumn()); - } - ; - - @Override - public String getDeleteTemplate() { - return format("DELETE FROM %s WHERE %s = ?", getQueueTable(), getIdColumn()); - } - - @Override - public String getFindByIdTemplate() { - return format("SELECT * FROM %s WHERE %s = ?", getQueueTable(), getIdColumn()); - } - - @Override - public String getUpdateTemplate() { - return format( - "UPDATE %s SET %s=?, %s=?%s, %s=? WHERE %s=?", - getQueueTable(), - getChannelColumn(), - getMessageColumn(), - getFormatJsonOperator(), - getInvokeAfterColumn(), - getIdColumn()); - } - - @Override - public String getDeleteLeaseTemplate() { - return format("DELETE FROM %s WHERE %s = ?;", getLeaseTableName(), getLeaseIdColumn()); - } - - @Override - public String getInsertLeaseTemplate() { - return format( - "INSERT INTO %s (%s, %s, %s, %s)" + "VALUES (?,?,?,?);", - getLeaseTableName(), - getLeaseIdColumn(), - getLeasedByColumn(), - getLeasedAtColumn(), - getLeaseDurationColumn()); - } - - @Override - public String getUpdateLeaseTemplate() { - return format( - "UPDATE %s SET %s=? WHERE %s = ?;", getQueueTable(), getLeaseIdColumn(), getIdColumn()); - } - - @Override - public String getFindLeaseByEntityTemplate() { - return format( - "SELECT * FROM %s WHERE %s = (SELECT lease_id FROM %s WHERE %s=? )", - getLeaseTableName(), getLeaseIdColumn(), getQueueTable(), getIdColumn()); - } - - protected String getFormatJsonOperator() { - return BaseSqlDialect.getJsonCastOperator(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/ObjectStoreStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/ObjectStoreStatements.java deleted file mode 100644 index 57f60988a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/ObjectStoreStatements.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema; - -public interface ObjectStoreStatements { - default String getObjectStoreTable() { - return "edc_cpadapter_object_store"; - } - - default String getIdColumn() { - return "id"; - } - - default String getCreatedAtColumn() { - return "created_at"; - } - - default String getTypeColumn() { - return "type"; - } - - default String getObjectColumn() { - return "object"; - } - - String getSaveObjectTemplate(); - - String getFindByIdAndTypeTemplate(); - - String getFindByTypeTemplate(); - - String getDeleteTemplate(); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/QueueStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/QueueStatements.java deleted file mode 100644 index 918734c2a..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/QueueStatements.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema; - -import org.eclipse.edc.sql.lease.LeaseStatements; - -/** Defines all statements that are needed for the ContractDefinition store */ -public interface QueueStatements extends LeaseStatements { - default String getQueueTable() { - return "edc_cpadapter_queue"; - } - - default String getIdColumn() { - return "id"; - } - - default String getCreatedAtColumn() { - return "created_at"; - } - - default String getChannelColumn() { - return "channel"; - } - - default String getMessageColumn() { - return "message"; - } - - default String getInvokeAfterColumn() { - return "invoke_after"; - } - - String getAllMessagesTemplate(); - - String getMessagesToSendTemplate(); - - String getSaveMessageTemplate(); - - String getDeleteTemplate(); - - String getFindByIdTemplate(); - - String getUpdateTemplate(); -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectObjectStoreStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectObjectStoreStatements.java deleted file mode 100644 index 85a078981..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectObjectStoreStatements.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema.postgres; - -import org.eclipse.edc.sql.dialect.PostgresDialect; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.BaseSqlDialectObjectStoreStatements; - -public class PostgresDialectObjectStoreStatements extends BaseSqlDialectObjectStoreStatements { - /** - * Overridable operator to convert strings to JSON. For postgres, this is the "::json" operator - */ - @Override - protected String getFormatJsonOperator() { - return PostgresDialect.getJsonCastOperator(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectQueueStatements.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectQueueStatements.java deleted file mode 100644 index cdafaf4f3..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/store/schema/postgres/PostgresDialectQueueStatements.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.store.schema.postgres; - -import org.eclipse.edc.sql.dialect.PostgresDialect; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.BaseSqlDialectQueueStatements; - -public class PostgresDialectQueueStatements extends BaseSqlDialectQueueStatements { - - /** - * Overridable operator to convert strings to JSON. For postgres, this is the "::json" operator - */ - @Override - protected String getFormatJsonOperator() { - return PostgresDialect.getJsonCastOperator(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMap.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMap.java deleted file mode 100644 index 657ca8f92..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMap.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.util; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class ExpiringMap { - private final Map map = new HashMap<>(); - private final Map entryTimeMap = new HashMap<>(); - private long expireAfter = 2 * 60; - - public ExpiringMap() {} - - public ExpiringMap(long expireAfter) { - this.expireAfter = expireAfter; - } - - public void put(K key, V value) { - map.put(key, value); - entryTimeMap.put(key, now()); - } - - public V get(K key) { - return get(key, expireAfter); - } - - public V get(K key, long expireAfter) { - V value = map.get(key); - - if (Objects.isNull(value)) { - return null; - } - - Long entryTime = entryTimeMap.get(key); - if (Objects.isNull(entryTime)) { - map.remove(key); - return null; - } - - if (entryTime + expireAfter < now()) { - return null; - } - - return value; - } - - public void remove(K key) { - map.remove(key); - entryTimeMap.remove(key); - } - - private long now() { - return Instant.now().getEpochSecond(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/LockMap.java b/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/LockMap.java deleted file mode 100644 index c8014ee9b..000000000 --- a/edc-extensions/control-plane-adapter/src/main/java/org/eclipse/tractusx/edc/cp/adapter/util/LockMap.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.util; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; - -/** - * When CP-Adapter works in InMemory mode, LockMap is used to prevent race condition of two events. - * This implementation will not work if both events will be handled by two separate EDC instances - * (persistent mode), but edc.cp.adapter.service.objectstore.ObjectStoreServiceSql#put(...) method - * will not allow to save both events in the table as PRIMARY_KE collision would appear. - */ -public class LockMap { - private final Map lock = new HashMap<>(); - - public void lock(String id) { - addLock(id); - lock.get(id).lock(); - } - - public void unlock(String id) { - addLock(id); - lock.get(id).unlock(); - } - - public void removeLock(String id) { - addLock(id); - lock.remove(id); - } - - private void addLock(String id) { - synchronized (this) { - lock.putIfAbsent(id, new ReentrantLock()); - } - } -} diff --git a/edc-extensions/control-plane-adapter/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/control-plane-adapter/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 30008be0f..000000000 --- a/edc-extensions/control-plane-adapter/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,14 +0,0 @@ -# -# Copyright (c) 2022 ZF Friedrichshafen AG -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# ZF Friedrichshafen AG - Initial API and Implementation -# - -org.eclipse.tractusx.edc.cp.adapter.ApiAdapterExtension diff --git a/edc-extensions/control-plane-adapter/src/main/resources/control-plane-adapter.jpg b/edc-extensions/control-plane-adapter/src/main/resources/control-plane-adapter.jpg deleted file mode 100644 index 00fd1241bc92df47f31b1bd81b84a84baa6fc880..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 205271 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�+M2z3LRL2cG4`^QzFm3dA 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

ji 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 IA@E`3zpWZF@c;k- diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/HttpControllerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/HttpControllerTest.java deleted file mode 100644 index 0d0f6add3..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/HttpControllerTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.when; - -import jakarta.ws.rs.core.Response; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.eclipse.tractusx.edc.cp.adapter.service.ResultService; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; - -public class HttpControllerTest { - @Mock ApiAdapterConfig config = Mockito.mock(ApiAdapterConfig.class); - Integer RETRY_NUMBER = 3; - - @Test - public void getAssetSynchronous_shouldReturnBadRequestIfNoAssetIdParam() { - // given - Monitor monitor = Mockito.mock(Monitor.class); - ResultService resultService = Mockito.mock(ResultService.class); - MessageBus messageBus = Mockito.mock(MessageBus.class); - when(config.getDefaultMessageRetryNumber()).thenReturn(RETRY_NUMBER); - HttpController httpController = new HttpController(monitor, resultService, messageBus, config); - - // when - Response response = httpController.getAssetSynchronous(null, "providerUrl", null, false, null); - - // then - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void getAssetSynchronous_shouldReturnBadRequestIfNoProviderUrlParam() { - // given - Monitor monitor = Mockito.mock(Monitor.class); - ResultService resultService = Mockito.mock(ResultService.class); - MessageBus messageBus = Mockito.mock(MessageBus.class); - ApiAdapterConfig config = Mockito.mock(ApiAdapterConfig.class); - when(config.getDefaultMessageRetryNumber()).thenReturn(RETRY_NUMBER); - HttpController httpController = new HttpController(monitor, resultService, messageBus, config); - - // when - Response response = httpController.getAssetSynchronous("assetId", null, null, false, null); - - // then - assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void getAssetSynchronous_shouldReturnErrorStatusIfOccurred() throws InterruptedException { - // given - Monitor monitor = Mockito.mock(Monitor.class); - ResultService resultService = Mockito.mock(ResultService.class); - MessageBus messageBus = Mockito.mock(MessageBus.class); - ApiAdapterConfig config = Mockito.mock(ApiAdapterConfig.class); - when(config.getDefaultMessageRetryNumber()).thenReturn(RETRY_NUMBER); - HttpController httpController = new HttpController(monitor, resultService, messageBus, config); - - when(resultService.pull(anyString())) - .thenReturn( - ProcessData.builder() - .errorStatus(Response.Status.BAD_GATEWAY) - .endpointDataReference(getEndpointDataReference()) - .build()); - - // when - Response response = - httpController.getAssetSynchronous("assetId", "providerUrl", null, false, null); - - // then - assertEquals(Response.Status.BAD_GATEWAY.getStatusCode(), response.getStatus()); - } - - @Test - public void getAssetSynchronous_shouldReturnOkResponse() throws InterruptedException { - // given - Monitor monitor = Mockito.mock(Monitor.class); - ResultService resultService = Mockito.mock(ResultService.class); - MessageBus messageBus = Mockito.mock(MessageBus.class); - ApiAdapterConfig config = Mockito.mock(ApiAdapterConfig.class); - when(config.getDefaultMessageRetryNumber()).thenReturn(RETRY_NUMBER); - HttpController httpController = new HttpController(monitor, resultService, messageBus, config); - when(resultService.pull(anyString())) - .thenReturn( - ProcessData.builder().endpointDataReference(getEndpointDataReference()).build()); - - // when - Response response = - httpController.getAssetSynchronous("assetId", "providerUrl", null, false, null); - - // then - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - } - - private EndpointDataReference getEndpointDataReference() { - return EndpointDataReference.Builder.newInstance() - .endpoint("endpoint") - .authCode("authCode") - .authKey("authKey") - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBusTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBusTest.java deleted file mode 100644 index abf500a96..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/InMemoryMessageBusTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class InMemoryMessageBusTest { - @Mock Monitor monitor; - @Mock Listener listener; - @Mock ListenerService listenerService; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void send_shouldCallListenerOnce() throws InterruptedException { - // given - Message message = new DataReferenceRetrievalDto(null, 3); - when(listenerService.getListener(any())).thenReturn(listener); - MessageBus messageBus = new InMemoryMessageBus(monitor, listenerService, 3); - - // when - messageBus.send(Channel.INITIAL, message); - - // then - Thread.sleep(50); - verify(listener, times(1)).process(any(DataReferenceRetrievalDto.class)); - } - - @Test - public void send_shouldCallListenerWithRetryOnException() throws InterruptedException { - // given - Message message = new DataReferenceRetrievalDto(null, 3); - when(listenerService.getListener(any())).thenReturn(listener); - doThrow(new IllegalStateException()).doNothing().when(listener).process(any()); - MessageBus messageBus = new InMemoryMessageBus(monitor, listenerService, 3); - - // when - messageBus.send(Channel.INITIAL, message); - - // then - Thread.sleep(1000); - verify(listener, times(2)).process(any(DataReferenceRetrievalDto.class)); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBusTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBusTest.java deleted file mode 100644 index a7a953c3c..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/messaging/SqlMessageBusTest.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.messaging; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.util.*; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; -import org.eclipse.edc.transaction.spi.TransactionContext; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.store.SqlQueueStore; -import org.eclipse.tractusx.edc.cp.adapter.store.model.QueueMessage; -import org.eclipse.tractusx.edc.cp.adapter.store.schema.QueueStatements; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class SqlMessageBusTest { - @Mock Monitor monitor; - @Mock Listener listener; - @Mock ListenerService listenerService; - @Mock SqlQueueStore store; - @Mock DataSourceRegistry dataSourceRegistry; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void send_shouldCallListenerOnce() throws InterruptedException { - // given - Message message = new DataReferenceRetrievalDto(null, 3); - when(listenerService.getListener(any())).thenReturn(listener); - SqlMessageBus messageBus = - new SqlMessageBus(monitor, listenerService, inMemoryFakeStore(), 2, 10); - - // when - messageBus.send(Channel.INITIAL, message); - Thread.sleep(60); - messageBus.deliverMessages(10); - - // then - Thread.sleep(60); - verify(listener, times(1)).process(any(DataReferenceRetrievalDto.class)); - } - - @Test - public void send_shouldCallListenerWithRetryOnException() throws InterruptedException { - // given - Message message = new DataReferenceRetrievalDto(null, 3); - when(listenerService.getListener(any())).thenReturn(listener); - doThrow(new IllegalStateException()).doNothing().when(listener).process(any()); - SqlMessageBus messageBus = - new SqlMessageBus(monitor, listenerService, inMemoryFakeStore(), 2, 10); - - // when - messageBus.send(Channel.INITIAL, message); - messageBus.deliverMessages(10); - Thread.sleep(60); - - // then - verify(listener, times(2)).process(any(DataReferenceRetrievalDto.class)); - } - - @Test - public void send_shouldSendToDlqIfErrorLimitReached() throws InterruptedException { - // given - Message message = new DataReferenceRetrievalDto(null, 3); - message.setErrorNumber(10); - when(listenerService.getListener(any())).thenReturn(listener); - doThrow(new IllegalStateException()).doNothing().when(listener).process(any()); - SqlMessageBus messageBus = - new SqlMessageBus(monitor, listenerService, inMemoryFakeStore(), 2, 10); - - // when - messageBus.send(Channel.INITIAL, message); - Thread.sleep(60); - - // then - verify(listenerService).getListener(eq(Channel.DLQ)); - } - - private SqlQueueStore inMemoryFakeStore() { - return new SqlQueueStore( - dataSourceRegistry, - "dsname", - getFakeTransactionContext(), - new ObjectMapper(), - getFakeStatements(), - "cid", - getFakeClock()) { - - private final Map map = new HashMap<>(); - - @Override - public void saveMessage(QueueMessage queueMessage) { - String id = UUID.randomUUID().toString(); - queueMessage.setId(id); - map.put(id, queueMessage); - } - - @Override - public QueueMessage findById(String id) { - return map.get(id); - } - - @Override - public void deleteMessage(String id) { - map.remove(id); - } - - @Override - public void updateMessage(QueueMessage queueMessage) { - map.remove(queueMessage.getId()); - map.put(queueMessage.getId(), queueMessage); - } - - @Override - public List findMessagesToSend(int max) { - return new ArrayList<>(map.values()); - } - }; - } - - private Clock getFakeClock() { - return new Clock() { - @Override - public ZoneId getZone() { - return null; - } - - @Override - public Clock withZone(ZoneId zone) { - return null; - } - - @Override - public Instant instant() { - return null; - } - }; - } - - @NotNull - private QueueStatements getFakeStatements() { - return new QueueStatements() { - @Override - public String getAllMessagesTemplate() { - return null; - } - - @Override - public String getMessagesToSendTemplate() { - return null; - } - - @Override - public String getSaveMessageTemplate() { - return null; - } - - @Override - public String getDeleteTemplate() { - return null; - } - - @Override - public String getFindByIdTemplate() { - return null; - } - - @Override - public String getUpdateTemplate() { - return null; - } - - @Override - public String getDeleteLeaseTemplate() { - return null; - } - - @Override - public String getInsertLeaseTemplate() { - return null; - } - - @Override - public String getUpdateLeaseTemplate() { - return null; - } - - @Override - public String getFindLeaseByEntityTemplate() { - return null; - } - }; - } - - private TransactionContext getFakeTransactionContext() { - return new TransactionContext() { - @Override - public void execute(TransactionBlock transactionBlock) {} - - @Override - public T execute(ResultTransactionBlock resultTransactionBlock) { - return null; - } - - @Override - public void registerSynchronization(TransactionSynchronization transactionSynchronization) {} - }; - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java deleted file mode 100644 index 22f1946ed..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/CatalogRetrieverTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.when; - -import java.time.ZonedDateTime; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.spi.catalog.CatalogService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.asset.Asset; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class CatalogRetrieverTest { - @Mock CatalogService catalogService; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void getEntireCatalog_shouldReturnEntireCatalogIfMoreThanOnePage() { - // given - CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); - when(catalogService.getByProviderUrl(anyString(), any())) - .thenReturn(getCatalogResult(50), getCatalogResult(50), getCatalogResult(10)); - - // when - Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); - - // then - assertEquals(110, catalog.getContractOffers().size()); - } - - @Test - public void getEntireCatalog_shouldEmptyCatalogIfNoResults() { - // given - CatalogRetriever catalogRetriever = new CatalogRetriever(50, catalogService); - when(catalogService.getByProviderUrl(anyString(), any())).thenReturn(getCatalogResult(0)); - - // when - Catalog catalog = catalogRetriever.getEntireCatalog("providerUrl", "assetId"); - - // then - assertEquals(0, catalog.getContractOffers().size()); - } - - private CompletableFuture getCatalogResult(int offersNumber) { - List contractOffers = - IntStream.range(0, offersNumber) - .mapToObj(operand -> getContractOffer()) - .collect(Collectors.toList()); - - return CompletableFuture.completedFuture( - Catalog.Builder.newInstance().id("id").contractOffers(contractOffers).build()); - } - - private ContractOffer getContractOffer() { - Asset asset = Asset.Builder.newInstance().id("assetId").build(); - return ContractOffer.Builder.newInstance() - .id("id") - .asset(asset) - .policy(Policy.Builder.newInstance().build()) - .contractStart(ZonedDateTime.now()) - .contractEnd(ZonedDateTime.now().plusDays(1)) - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java deleted file mode 100644 index ad4cda3c0..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractAgreementRetrieverTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -import java.time.Instant; -import java.util.stream.Stream; -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.service.spi.result.ServiceResult; -import org.eclipse.edc.spi.monitor.Monitor; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class ContractAgreementRetrieverTest { - @Mock Monitor monitor; - @Mock ContractAgreementService agreementService; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void getExistingContractByAssetId_shouldReturnValidContract() { - // given - long now = Instant.now().getEpochSecond(); - when(agreementService.query(any())).thenReturn(getResult(now + 1000)); - ContractAgreementRetriever retriever = - new ContractAgreementRetriever(monitor, agreementService); - - // when - ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); - - // then - Assertions.assertNotNull(contractAgreement); - } - - @Test - public void getExistingContractByAssetId_shouldNotReturnExpiredContract() { - // given - long now = Instant.now().getEpochSecond(); - when(agreementService.query(any())).thenReturn(getResult(now - 1000)); - ContractAgreementRetriever retriever = - new ContractAgreementRetriever(monitor, agreementService); - - // when - ContractAgreement contractAgreement = retriever.getExistingContractByAssetId("id"); - - // then - Assertions.assertNull(contractAgreement); - } - - private ServiceResult> getResult(long endDate) { - long now = Instant.now().getEpochSecond(); - return ServiceResult.success( - Stream.of( - ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 2000) - .contractEndDate(endDate) - .providerAgentId("providerId") - .consumerAgentId("consumerId") - .policy(Policy.Builder.newInstance().build()) - .build())); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java deleted file mode 100644 index 7d82766a9..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnegotiation/ContractNegotiationHandlerTest.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnegotiation; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; - -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.List; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.asset.Asset; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class ContractNegotiationHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNegotiationService contractNegotiationService; - @Mock CatalogCachedRetriever catalogRetriever; - @Mock ContractAgreementRetriever agreementRetriever; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void process_shouldNotInitializeContractNegotiationWhenContractAlreadyAvailable() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())) - .thenReturn(getValidContractAgreement()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(0)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - @Test - public void process_shouldInitializeContractNegotiationWhenExistingContractExpired() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())) - .thenReturn(getExpiredContractAgreement()); - when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) - .thenReturn(getCatalog()); - when(contractNegotiationService.initiateNegotiation(any())) - .thenReturn(getContractNegotiation()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(1)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - @Test - public void process_shouldInitiateContractNegotiationAndSendDtoFurtherIfAgreementNotExist() { - // given - ContractNegotiationHandler contractNegotiationHandler = - new ContractNegotiationHandler( - monitor, messageBus, contractNegotiationService, catalogRetriever, agreementRetriever); - - when(agreementRetriever.getExistingContractByAssetId(anyString())).thenReturn(null); - when(catalogRetriever.getEntireCatalog(anyString(), anyString(), anyInt())) - .thenReturn(getCatalog()); - when(contractNegotiationService.initiateNegotiation(any())) - .thenReturn(getContractNegotiation()); - - // when - contractNegotiationHandler.process(new DataReferenceRetrievalDto(getProcessData(), 3)); - - // then - verify(contractNegotiationService, times(1)).initiateNegotiation(any()); - verify(messageBus, times(1)).send(any(), any(Message.class)); - } - - private ProcessData getProcessData() { - return ProcessData.builder() - .assetId("assetId") - .provider("provider") - .catalogExpiryTime(30) - .contractAgreementReuseOn(true) - .build(); - } - - private ContractNegotiation getContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .build(); - } - - private Catalog getCatalog() { - return Catalog.Builder.newInstance() - .id("id") - .contractOffers(List.of(getContractOffer())) - .build(); - } - - private ContractOffer getContractOffer() { - Asset asset = Asset.Builder.newInstance().id("assetId").build(); - return ContractOffer.Builder.newInstance() - .id("id") - .asset(asset) - .contractStart(ZonedDateTime.now()) - .contractEnd(ZonedDateTime.now().plusDays(1)) - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private ContractAgreement getValidContractAgreement() { - long now = Instant.now().getEpochSecond(); - return ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 5000) - .contractEndDate(now + 5000) - .consumerAgentId("consumer") - .providerAgentId("provider") - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private ContractAgreement getExpiredContractAgreement() { - long now = Instant.now().getEpochSecond(); - return ContractAgreement.Builder.newInstance() - .id("id") - .assetId("assetId") - .contractStartDate(now - 5000) - .contractEndDate(now - 1000) - .consumerAgentId("consumer") - .providerAgentId("provider") - .policy(Policy.Builder.newInstance().build()) - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java deleted file mode 100644 index 5d0e83553..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNegotiationListenerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.times; - -import jakarta.ws.rs.core.Response; -import org.eclipse.edc.connector.contract.spi.negotiation.observe.ContractNegotiationListener; -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class ContractNegotiationListenerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNotificationSyncService syncService; - @Mock DataTransferInitializer dataTransfer; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void confirmed_shouldNotInitiateTransferIfMessageNotAvailable() { - // given - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.confirmed(contractNegotiation); - - // then - verify(syncService, times(1)).exchangeConfirmedContract(any(), any()); - verify(dataTransfer, times(0)).initiate(any()); - verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void confirmed_shouldInitiateTransferIfMessageIsAvailable() { - // given - when(syncService.exchangeConfirmedContract(any(), any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - verify(dataTransfer, times(0)).initiate(any()); - - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.confirmed(contractNegotiation); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void preDeclined_shouldSendErrorResultIfMessageIsAvailable() { - // given - when(syncService.exchangeDeclinedContract(any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.declined(contractNegotiation); - - // then - ArgumentCaptor messageArg = - ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); - verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); - Assertions.assertEquals( - Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); - } - - @Test - public void preError_shouldSendErrorResultIfMessageIsAvailable() { - // given - when(syncService.exchangeErrorContract(any())) - .thenReturn(new DataReferenceRetrievalDto(getProcessData(), 3)); - ContractNegotiationListener listener = - new ContractNegotiationListenerImpl(monitor, messageBus, syncService, dataTransfer); - ContractNegotiation contractNegotiation = getConfirmedContractNegotiation(); - - // when - listener.failed(contractNegotiation); - - // then - ArgumentCaptor messageArg = - ArgumentCaptor.forClass(DataReferenceRetrievalDto.class); - verify(messageBus, times(1)).send(eq(Channel.RESULT), messageArg.capture()); - Assertions.assertEquals( - Response.Status.BAD_GATEWAY, messageArg.getValue().getPayload().getErrorStatus()); - } - - private ContractNegotiation getConfirmedContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .state(ContractNegotiationStates.CONFIRMED.code()) - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .contractAgreement( - ContractAgreement.Builder.newInstance() - .id("contractAgreementId") - .providerAgentId("providerAgentId") - .consumerAgentId("consumerAgentId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()) - .build()) - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java deleted file mode 100644 index c2e0480b3..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractNotificationHandlerTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; -import org.eclipse.edc.connector.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class ContractNotificationHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock ContractNegotiationService contractNegotiationService; - @Mock ContractNotificationSyncService syncService; - @Mock DataTransferInitializer dataTransfer; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void process_shouldNotInitiateTransferWhenNoContractNotification() { - // given - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(syncService, times(1)).exchangeDto(any()); - verify(dataTransfer, times(0)).initiate(any()); - verify(messageBus, times(0)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractConfirmedFromCache() { - // given - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - dto.getPayload().setContractConfirmed(true); - - // when - contractNotificationHandler.process(dto); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractAlreadyConfirmedAtProvider() { - // given - when(contractNegotiationService.findbyId(any())).thenReturn(getConfirmedContractNegotiation()); - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(syncService, times(0)).exchangeDto(any()); - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - } - - @Test - public void process_shouldInitiateTransferWhenContractConfirmedByNotification() { - // given - when(syncService.exchangeDto(any())) - .thenReturn( - new ContractInfo("confirmedContractAgreementId", ContractInfo.ContractState.CONFIRMED)); - ContractNotificationHandler contractNotificationHandler = - new ContractNotificationHandler( - monitor, messageBus, syncService, contractNegotiationService, dataTransfer); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - contractNotificationHandler.process(dto); - - // then - verify(dataTransfer, times(1)).initiate(any()); - verify(messageBus, times(1)).send(eq(Channel.DATA_REFERENCE), any(Message.class)); - verify(syncService, times(1)).removeContractInfo(any()); - } - - private ContractNegotiation getConfirmedContractNegotiation() { - return ContractNegotiation.Builder.newInstance() - .state(ContractNegotiationStates.CONFIRMED.code()) - .id("contractNegotiationId") - .counterPartyId("counterPartyId") - .counterPartyAddress("counterPartyAddress") - .protocol("protocol") - .contractAgreement( - ContractAgreement.Builder.newInstance() - .id("contractAgreementId") - .providerAgentId("providerAgentId") - .consumerAgentId("consumerAgentId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()) - .build()) - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncServiceTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncServiceTest.java deleted file mode 100644 index 720cc0b37..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/contractnotification/ContractSyncServiceTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.contractnotification; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreServiceInMemory; -import org.eclipse.tractusx.edc.cp.adapter.util.LockMap; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class ContractSyncServiceTest { - - @Test - public void exchangeConfirmedContract_shouldReturnDtoIfAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeDto(getDataReferenceRetrievalDto()); - - // when - DataReferenceRetrievalDto dto = - syncService.exchangeConfirmedContract("negotiationId", "agreementId"); - - // then - Assertions.assertNotNull(dto); - } - - @Test - public void exchangeConfirmedContract_shouldReturnNullIfDtoNotAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - DataReferenceRetrievalDto dto = - syncService.exchangeConfirmedContract("negotiationId", "agreementId"); - - // then - Assertions.assertNull(dto); - } - - @Test - public void exchangeDeclinedContract_shouldReturnDtoIfAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeDto(getDataReferenceRetrievalDto()); - - // when - DataReferenceRetrievalDto dto = syncService.exchangeDeclinedContract("negotiationId"); - - // then - Assertions.assertNotNull(dto); - } - - @Test - public void exchangeDeclinedContract_shouldReturnNullIfDtoNotAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - DataReferenceRetrievalDto dto = syncService.exchangeDeclinedContract("negotiationId"); - - // then - Assertions.assertNull(dto); - } - - @Test - public void exchangeErrorContract_shouldReturnDtoIfAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeDto(getDataReferenceRetrievalDto()); - - // when - DataReferenceRetrievalDto dto = syncService.exchangeErrorContract("negotiationId"); - - // then - Assertions.assertNotNull(dto); - } - - @Test - public void exchangeErrorContract_shouldReturnNullIfDtoNotAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - DataReferenceRetrievalDto dto = syncService.exchangeErrorContract("negotiationId"); - - // then - Assertions.assertNull(dto); - } - - @Test - public void exchangeDto_shouldReturnContractInfoIfAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeConfirmedContract("negotiationId", "agreementId"); - - // when - ContractInfo contractInfo = syncService.exchangeDto(getDataReferenceRetrievalDto()); - - // then - Assertions.assertNotNull(contractInfo); - } - - @Test - public void exchangeDto_shouldReturnNullIfContractInfoNotAvailable() { - // given - ContractSyncService syncService = - new ContractSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - ContractInfo contractInfo = syncService.exchangeDto(getDataReferenceRetrievalDto()); - - // then - Assertions.assertNull(contractInfo); - } - - private DataReferenceRetrievalDto getDataReferenceRetrievalDto() { - ProcessData processData = ProcessData.builder().assetId("assetId").provider("provider").build(); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(processData, 3); - dto.getPayload().setContractNegotiationId("negotiationId"); - return dto; - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncServiceTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncServiceTest.java deleted file mode 100644 index 0b0e537f7..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataRefSyncServiceTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreServiceInMemory; -import org.eclipse.tractusx.edc.cp.adapter.util.LockMap; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class DataRefSyncServiceTest { - - @Test - public void exchangeDto_shouldReturnDataReferenceIfAvailable() { - // given - DataRefSyncService syncService = - new DataRefSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeDataReference(getEndpointDataReference(), "agreementId"); - - // when - EndpointDataReference dataReference = - syncService.exchangeDto(getDataReferenceRetrievalDto(), "agreementId"); - - // then - Assertions.assertNotNull(dataReference); - } - - @Test - public void exchangeDto_shouldReturnNullIfDataReferenceNotAvailable() { - // given - DataRefSyncService syncService = - new DataRefSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - EndpointDataReference dataReference = - syncService.exchangeDto(getDataReferenceRetrievalDto(), "agreementId"); - - // then - Assertions.assertNull(dataReference); - } - - @Test - public void exchangeDataReference_shouldReturnDtoIfAvailable() { - // given - DataRefSyncService syncService = - new DataRefSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - syncService.exchangeDto(getDataReferenceRetrievalDto(), "agreementId"); - - // when - DataReferenceRetrievalDto dto = - syncService.exchangeDataReference(getEndpointDataReference(), "agreementId"); - - // then - Assertions.assertNotNull(dto); - } - - @Test - public void exchangeDataReference_shouldReturnNullIfDtoNotAvailable() { - // given - DataRefSyncService syncService = - new DataRefSyncService(new ObjectStoreServiceInMemory(new ObjectMapper()), new LockMap()); - - // when - DataReferenceRetrievalDto dto = - syncService.exchangeDataReference(getEndpointDataReference(), "agreementId"); - - // then - Assertions.assertNull(dto); - } - - private EndpointDataReference getEndpointDataReference() { - return EndpointDataReference.Builder.newInstance() - .endpoint("endpoint") - .authCode("authCode") - .authKey("authKey") - .build(); - } - - private DataReferenceRetrievalDto getDataReferenceRetrievalDto() { - ProcessData processData = ProcessData.builder().assetId("assetId").provider("provider").build(); - return new DataReferenceRetrievalDto(processData, 3); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java deleted file mode 100644 index 167a47479..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceErrorHandlerTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.edc.connector.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreService; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectStoreServiceInMemory; -import org.eclipse.tractusx.edc.cp.adapter.service.objectstore.ObjectType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class DataReferenceErrorHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock TransferProcessService transferProcessService; - ObjectStoreService storeService = new ObjectStoreServiceInMemory(new ObjectMapper()); - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - storeService.put("key1", ObjectType.DTO, getDto()); - storeService.put("key2", ObjectType.DTO, getDto()); - } - - @Test - public void validateActiveProcesses_shouldSkipIfNoError() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(0)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void validateActiveProcesses_shouldHandleErrorReference() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()) - .thenReturn(TransferProcessStates.ERROR.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void validateActiveProcesses_shouldHandleCancelledReference() { - // given - when(transferProcessService.getState("transferId")) - .thenReturn(TransferProcessStates.COMPLETED.name()) - .thenReturn(TransferProcessStates.CANCELLED.name()); - DataReferenceErrorHandler errorHandler = - new DataReferenceErrorHandler(monitor, messageBus, storeService, transferProcessService); - - // when - errorHandler.validateActiveProcesses(); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - } - - private DataReferenceRetrievalDto getDto() { - return new DataReferenceRetrievalDto(getProcessData(), 3); - } - - private ProcessData getProcessData() { - return ProcessData.builder() - .assetId("assetId") - .provider("provider") - .contractAgreementId("agreementId") - .transferProcessId("transferId") - .build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandlerTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandlerTest.java deleted file mode 100644 index 138dbe974..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/DataReferenceHandlerTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class DataReferenceHandlerTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock DataRefNotificationSyncService notificationSyncService; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void process_shouldNotSendResultWhenDataReferenceNotAvailable() { - // given - DataReferenceHandler dataReferenceHandler = - new DataReferenceHandler(monitor, messageBus, notificationSyncService); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - dataReferenceHandler.process(dto); - - // then - verify(notificationSyncService, times(1)).exchangeDto(eq(dto), any()); - verify(messageBus, times(0)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void process_shouldSendResultWhenDataReferenceIsAvailable() { - // given - when(notificationSyncService.exchangeDto(any(), any())).thenReturn(getEndpointDataReference()); - DataReferenceHandler dataReferenceHandler = - new DataReferenceHandler(monitor, messageBus, notificationSyncService); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - - // when - dataReferenceHandler.process(dto); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - verify(notificationSyncService, times(1)).removeDataReference(any()); - } - - private EndpointDataReference getEndpointDataReference() { - return EndpointDataReference.Builder.newInstance() - .endpoint("endpoint") - .authCode("authCode") - .authKey("authKey") - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverTest.java deleted file mode 100644 index 9d0d983c4..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/process/datareference/EndpointDataReferenceReceiverTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.process.datareference; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -import org.eclipse.edc.connector.transfer.spi.edr.EndpointDataReferenceReceiver; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Channel; -import org.eclipse.tractusx.edc.cp.adapter.messaging.Message; -import org.eclipse.tractusx.edc.cp.adapter.messaging.MessageBus; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class EndpointDataReferenceReceiverTest { - @Mock Monitor monitor; - @Mock MessageBus messageBus; - @Mock DataRefNotificationSyncService syncService; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void send_shouldNotSendResultWhenMessageNotAvailable() { - // given - EndpointDataReferenceReceiver referenceReceiver = - new EndpointDataReferenceReceiverImpl(monitor, messageBus, syncService); - - // when - referenceReceiver.send(getEndpointDataReference()); - - // then - verify(messageBus, times(0)).send(eq(Channel.RESULT), any(Message.class)); - } - - @Test - public void send_shouldSendResultWhenMessageIsAvailable() { - // given - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - when(syncService.exchangeDataReference(any(), any())).thenReturn(dto); - EndpointDataReferenceReceiver referenceReceiver = - new EndpointDataReferenceReceiverImpl(monitor, messageBus, syncService); - - // when - referenceReceiver.send(getEndpointDataReference()); - - // then - verify(messageBus, times(1)).send(eq(Channel.RESULT), any(Message.class)); - verify(syncService, times(1)).removeDto(any()); - } - - private EndpointDataReference getEndpointDataReference() { - return EndpointDataReference.Builder.newInstance() - .endpoint("endpoint") - .authCode("authCode") - .authKey("authKey") - .build(); - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java deleted file mode 100644 index 0c4c649de..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/service/ResultServiceTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.service; - -import static org.junit.jupiter.api.Assertions.fail; - -import java.util.concurrent.TimeUnit; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.cp.adapter.dto.DataReferenceRetrievalDto; -import org.eclipse.tractusx.edc.cp.adapter.dto.ProcessData; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class ResultServiceTest { - @Mock Monitor monitor; - - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - - @Test - public void pull_shouldReturnDataReferenceWhenMessageOccursFirst() throws InterruptedException { - // given - ResultService resultService = new ResultService(20, monitor); - String endpointDataRefId = "456"; - DataReferenceRetrievalDto dto = getDto(endpointDataRefId); - ProcessData processData; - - // when - resultService.process(dto); - processData = resultService.pull(dto.getTraceId(), 200, TimeUnit.MILLISECONDS); - - // then - Assertions.assertEquals(endpointDataRefId, processData.getEndpointDataReference().getId()); - } - - @Test - public void pull_shouldReturnDataReferenceWhenMessageOccursSecond() throws InterruptedException { - // given - ResultService resultService = new ResultService(20, monitor); - String endpointDataRefId = "456"; - DataReferenceRetrievalDto dto = getDto(endpointDataRefId); - ProcessData processData; - - // when - processMessageWithDelay(resultService, dto); - processData = resultService.pull(dto.getTraceId(), 1000, TimeUnit.MILLISECONDS); - - // then - Assertions.assertEquals(endpointDataRefId, processData.getEndpointDataReference().getId()); - } - - @Test - public void pull_shouldReturnNullOnTimeout() throws InterruptedException { - // given - ResultService resultService = new ResultService(20, monitor); - - // when - ProcessData processData = resultService.pull("123", 500, TimeUnit.MILLISECONDS); - - // then - Assertions.assertNull(processData); - } - - @Test - public void process_shouldThrowIllegalArgumentExceptionIfNoDataPayload() { - // given - ResultService resultService = new ResultService(20, monitor); - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(null, 3); - - // when then - try { - resultService.process(dto); - fail("Method should throw IllegalArgumentException"); - } catch (IllegalArgumentException ignored) { - } - } - - private void processMessageWithDelay(ResultService resultService, DataReferenceRetrievalDto dto) { - new Thread( - () -> { - sleep(400); - resultService.process(dto); - }) - .start(); - } - - private DataReferenceRetrievalDto getDto(String endpointDataRefId) { - DataReferenceRetrievalDto dto = new DataReferenceRetrievalDto(getProcessData(), 3); - dto.getPayload() - .setEndpointDataReference( - EndpointDataReference.Builder.newInstance() - .id(endpointDataRefId) - .endpoint("e") - .authCode("c") - .authKey("k") - .build()); - return dto; - } - - private void sleep(long milisec) { - try { - Thread.sleep(milisec); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - private ProcessData getProcessData() { - return ProcessData.builder().assetId("assetId").provider("provider").build(); - } -} diff --git a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMapTest.java b/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMapTest.java deleted file mode 100644 index a9ed2a9b5..000000000 --- a/edc-extensions/control-plane-adapter/src/test/java/org/eclipse/tractusx/edc/cp/adapter/util/ExpiringMapTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022 ZF Friedrichshafen AG - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * ZF Friedrichshafen AG - Initial API and Implementation - * - */ - -package org.eclipse.tractusx.edc.cp.adapter.util; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -public class ExpiringMapTest { - - private static final String KEY = "key"; - private static final String VAL = "value"; - - @Test - public void get_shouldGetWhenNotExpired() { - // given - ExpiringMap expiringMap = new ExpiringMap<>(60); - expiringMap.put(KEY, VAL); - - // when - String value = expiringMap.get(KEY); - - // then - Assertions.assertEquals(VAL, value); - } - - @Test - public void get_shouldGetNullWhenExpired() throws InterruptedException { - // given - ExpiringMap expiringMap = new ExpiringMap<>(0); - expiringMap.put(KEY, VAL); - - // when - Thread.sleep(1000); - String value = expiringMap.get(KEY, 0); - - // then - Assertions.assertNull(value); - } - - @Test - public void get_shouldGetNullWhenRemoved() throws InterruptedException { - // given - ExpiringMap expiringMap = new ExpiringMap<>(0); - expiringMap.put(KEY, VAL); - - // when - expiringMap.remove(KEY); - String value = expiringMap.get(KEY, 1000); - - // then - Assertions.assertNull(value); - } -} diff --git a/edc-extensions/cx-oauth2/build.gradle.kts b/edc-extensions/cx-oauth2/build.gradle.kts index 5d25556f0..16d6219ae 100644 --- a/edc-extensions/cx-oauth2/build.gradle.kts +++ b/edc-extensions/cx-oauth2/build.gradle.kts @@ -24,9 +24,9 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.oauth2) - implementation(edc.spi.jwt) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.oauth2) + implementation(libs.edc.spi.jwt) implementation(libs.slf4j.api) implementation(libs.nimbus.jwt) implementation(libs.okhttp) diff --git a/edc-extensions/data-encryption/build.gradle.kts b/edc-extensions/data-encryption/build.gradle.kts index 8f5a67de0..79603708c 100644 --- a/edc-extensions/data-encryption/build.gradle.kts +++ b/edc-extensions/data-encryption/build.gradle.kts @@ -23,7 +23,7 @@ plugins { } dependencies { - api(edc.spi.core) - implementation(edc.spi.dataplane.transfer) - implementation(libs.bouncyCastle.bcpkix) + api(libs.edc.spi.core) + implementation(libs.edc.spi.dataplane.transfer) + implementation(libs.bouncyCastle.bcpkixJdk18on) } diff --git a/edc-extensions/dataplane-selector-configuration/build.gradle.kts b/edc-extensions/dataplane-selector-configuration/build.gradle.kts index ba7eff87d..bba41eac6 100644 --- a/edc-extensions/dataplane-selector-configuration/build.gradle.kts +++ b/edc-extensions/dataplane-selector-configuration/build.gradle.kts @@ -23,9 +23,9 @@ plugins { } dependencies { - api(edc.spi.core) - api(edc.junit) - implementation(edc.spi.dataplane.selector) - implementation(edc.spi.dataplane.transfer) - implementation(libs.bouncyCastle.bcpkix) + api(libs.edc.spi.core) + api(libs.edc.junit) + implementation(libs.edc.spi.dataplane.selector) + implementation(libs.edc.spi.dataplane.transfer) + implementation(libs.bouncyCastle.bcpkixJdk18on) } diff --git a/edc-extensions/hashicorp-vault/build.gradle.kts b/edc-extensions/hashicorp-vault/build.gradle.kts index 20d5c0aa4..caa93d104 100644 --- a/edc-extensions/hashicorp-vault/build.gradle.kts +++ b/edc-extensions/hashicorp-vault/build.gradle.kts @@ -23,9 +23,9 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.junit) - implementation(libs.bouncyCastle.bcpkix) + implementation(libs.edc.spi.core) + implementation(libs.edc.junit) + implementation(libs.bouncyCastle.bcpkixJdk18on) implementation(libs.okhttp) implementation("org.testcontainers:vault:1.18.1") implementation("org.testcontainers:junit-jupiter:1.18.1") diff --git a/edc-extensions/observability-api-customization/build.gradle.kts b/edc-extensions/observability-api-customization/build.gradle.kts index 33f1920d1..eb9141e23 100644 --- a/edc-extensions/observability-api-customization/build.gradle.kts +++ b/edc-extensions/observability-api-customization/build.gradle.kts @@ -24,19 +24,19 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.web) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.web) // provides the web server - runtimeOnly(edc.ext.http) + runtimeOnly(libs.edc.ext.http) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.restAssured) // needed for auto-registering the Auth Service: - testImplementation(edc.api.management) + testImplementation(libs.edc.api.management) // provides token-based authentication at test runtime - testRuntimeOnly(edc.auth.tokenbased) + testRuntimeOnly(libs.edc.auth.tokenbased) } diff --git a/edc-extensions/postgresql-migration/build.gradle.kts b/edc-extensions/postgresql-migration/build.gradle.kts index 5b90d57df..62c6c7bb3 100644 --- a/edc-extensions/postgresql-migration/build.gradle.kts +++ b/edc-extensions/postgresql-migration/build.gradle.kts @@ -23,11 +23,12 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.junit) - implementation(edc.spi.transaction.datasource) - implementation(edc.sql.assetindex) - implementation(edc.sql.core) + implementation(libs.edc.spi.core) + implementation(libs.edc.junit) + implementation(libs.edc.spi.transaction.datasource) + implementation(libs.edc.sql.assetindex) + implementation(libs.edc.sql.core) + runtimeOnly(libs.postgres) implementation("org.flywaydb:flyway-core:9.18.0") } diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/asset/V0_0_4__Alter_Asset_Property_add_IsPrivateFlag.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/asset/V0_0_4__Alter_Asset_Property_add_IsPrivateFlag.sql new file mode 100644 index 000000000..636b9636f --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/asset/V0_0_4__Alter_Asset_Property_add_IsPrivateFlag.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Mercedes-Benz Tech Innovation GmbH - EDC Snapshot 20220815 Update +-- + +-- add columns +ALTER TABLE edc_asset_property + ADD COLUMN property_is_private BOOLEAN; diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractdefinition/V0_0_5__Alter_ContractDefinition_Remove_validity.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractdefinition/V0_0_5__Alter_ContractDefinition_Remove_validity.sql new file mode 100644 index 000000000..266a668c4 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractdefinition/V0_0_5__Alter_ContractDefinition_Remove_validity.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Mercedes-Benz Tech Innovation GmbH - EDC Snapshot 20221201 Update +-- + +-- add columns +ALTER TABLE edc_contract_definitions + DROP COLUMN validity; diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql new file mode 100644 index 000000000..2caff7099 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_5__Alter_ContractNegotiation_Add_Callbacks.sql @@ -0,0 +1,15 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- add column +ALTER TABLE edc_contract_negotiation ADD COLUMN callback_addresses JSON; diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_6__Alter_ContractNegotiation_Change_Type.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_6__Alter_ContractNegotiation_Change_Type.sql new file mode 100644 index 000000000..0c4c58f7f --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_6__Alter_ContractNegotiation_Change_Type.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- alter type of column "type" +ALTER TABLE edc_contract_negotiation + ALTER COLUMN type TYPE VARCHAR \ No newline at end of file diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_8__Alter_TransferProcess_Add_Callbacks.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_8__Alter_TransferProcess_Add_Callbacks.sql new file mode 100644 index 000000000..0ef0dd9d1 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_8__Alter_TransferProcess_Add_Callbacks.sql @@ -0,0 +1,15 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- add column +ALTER TABLE edc_transfer_process ADD COLUMN callback_addresses JSON; diff --git a/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_9__Alter_TransferProcess_Remove_Transfertype.sql b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_9__Alter_TransferProcess_Remove_Transfertype.sql new file mode 100644 index 000000000..34fdc3254 --- /dev/null +++ b/edc-extensions/postgresql-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/transferprocess/V0_0_9__Alter_TransferProcess_Remove_Transfertype.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +-- + +-- add column +ALTER TABLE edc_transfer_process + DROP COLUMN IF EXISTS transfer_type; diff --git a/edc-extensions/provision-additional-headers/build.gradle.kts b/edc-extensions/provision-additional-headers/build.gradle.kts index 1ac640a88..a8d00caed 100644 --- a/edc-extensions/provision-additional-headers/build.gradle.kts +++ b/edc-extensions/provision-additional-headers/build.gradle.kts @@ -23,12 +23,15 @@ plugins { } dependencies { - implementation(edc.spi.core) - implementation(edc.spi.transfer) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) testImplementation(libs.awaitility) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) - testImplementation(edc.core.controlplane) + testImplementation(libs.edc.core.controlplane) + testImplementation(libs.edc.dpf.selector.core) + testImplementation(libs.edc.dsp) + testImplementation(libs.edc.iam.mock) testImplementation(libs.mockito.inline) } diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java index 72d68b631..1e3f72754 100644 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java +++ b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java @@ -20,21 +20,33 @@ package org.eclipse.tractusx.edc.provision.additionalheaders; -import org.eclipse.edc.connector.transfer.spi.TransferProcessManager; +import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; +import org.eclipse.edc.connector.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.connector.contract.spi.validation.ContractValidationService; +import org.eclipse.edc.connector.spi.transferprocess.TransferProcessProtocolService; import org.eclipse.edc.connector.transfer.spi.flow.DataFlowController; import org.eclipse.edc.connector.transfer.spi.flow.DataFlowManager; -import org.eclipse.edc.connector.transfer.spi.types.DataRequest; +import org.eclipse.edc.connector.transfer.spi.types.DataFlowResponse; +import org.eclipse.edc.connector.transfer.spi.types.protocol.TransferRequestMessage; import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.extensions.EdcExtension; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.service.spi.result.ServiceResult; import org.eclipse.edc.spi.asset.AssetIndex; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.asset.Asset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; @@ -46,46 +58,62 @@ @ExtendWith(EdcExtension.class) class ProvisionAdditionalHeadersExtensionTest { - private final DataFlowController dataFlowController = mock(DataFlowController.class); - - @BeforeEach - void setUp() { - when(dataFlowController.canHandle(any(), any())).thenReturn(true); - when(dataFlowController.initiateFlow(any(), any(), any())).thenReturn(StatusResult.success()); - } - - @Test - void shouldPutContractIdAsHeaderInDataAddress( - TransferProcessManager transferProcessManager, - AssetIndex assetIndex, - DataFlowManager dataFlowManager) { - dataFlowManager.register(dataFlowController); - var asset = Asset.Builder.newInstance().id("assetId").build(); - var dataAddress = DataAddress.Builder.newInstance().type("HttpData").build(); - assetIndex.accept(asset, dataAddress); - - var dataRequest = - DataRequest.Builder.newInstance() - .contractId("aContractId") - .assetId("assetId") - .destinationType("HttpProxy") - .build(); - - var result = transferProcessManager.initiateProviderRequest(dataRequest); - - assertThat(result).matches(StatusResult::succeeded); - - await() - .untilAsserted( - () -> { - verify(dataFlowController) - .initiateFlow( - any(), - argThat( - it -> - "aContractId" - .equals(it.getProperty("header:Edc-Contract-Agreement-Id"))), - any()); - }); - } + private final DataFlowController dataFlowController = mock(DataFlowController.class); + private final RemoteMessageDispatcherRegistry dispatcherRegistry = mock(RemoteMessageDispatcherRegistry.class); + + private ContractNegotiationStore contractNegotiationStore = mock(ContractNegotiationStore.class); + private ContractValidationService contractValidationService = mock(ContractValidationService.class); + + @BeforeEach + void setUp(EdcExtension extension) { + extension.setConfiguration(Map.of("edc.ids.id", "urn:connector:test")); + when(dataFlowController.canHandle(any(), any())).thenReturn(true); + when(dataFlowController.initiateFlow(any(), any(), any())).thenReturn(StatusResult.success(DataFlowResponse.Builder.newInstance().build())); + extension.registerServiceMock(RemoteMessageDispatcherRegistry.class, dispatcherRegistry); + extension.registerServiceMock(ContractNegotiationStore.class, contractNegotiationStore); + extension.registerServiceMock(ContractValidationService.class, contractValidationService); + } + + @Test + void shouldPutContractIdAsHeaderInDataAddress( + TransferProcessProtocolService transferProcessProtocolService, + AssetIndex assetIndex, + DataFlowManager dataFlowManager) { + + var agreement = ContractAgreement.Builder.newInstance() + .id("aContractId") + .providerId("provider") + .consumerId("consumer") + .policy(Policy.Builder.newInstance().build()) + .assetId("assetId") + .build(); + + when(dispatcherRegistry.send(any(), any())).thenReturn(CompletableFuture.completedFuture(null)); + when(contractNegotiationStore.findContractAgreement(any())).thenReturn(agreement); + when(contractValidationService.validateAgreement(any(), any())).thenReturn(Result.success(agreement)); + + dataFlowManager.register(dataFlowController); + var asset = Asset.Builder.newInstance().id("assetId").build(); + var dataAddress = DataAddress.Builder.newInstance().type("HttpData").build(); + assetIndex.create(asset, dataAddress); + + var transferMessage = TransferRequestMessage.Builder.newInstance() + .id("id") + .protocol("protocol") + .assetId("assetId") + .contractId("aContractId") + .dataDestination(DataAddress.Builder.newInstance().type("HttpProxy").build()) + .callbackAddress("callbackAddress") + .build(); + + var result = transferProcessProtocolService.notifyRequested(transferMessage, ClaimToken.Builder.newInstance().build()); + + assertThat(result).matches(ServiceResult::succeeded); + + await().untilAsserted( + () -> { + verify(dataFlowController) + .initiateFlow(any(), argThat(it -> "aContractId".equals(it.getProperty("header:Edc-Contract-Agreement-Id"))), any()); + }); + } } diff --git a/edc-extensions/transferprocess-sftp-client/build.gradle.kts b/edc-extensions/transferprocess-sftp-client/build.gradle.kts index 67ae01bfb..7258805b8 100644 --- a/edc-extensions/transferprocess-sftp-client/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-client/build.gradle.kts @@ -24,20 +24,20 @@ plugins { dependencies { implementation(project(":edc-extensions:transferprocess-sftp-common")) - implementation(edc.spi.core) - implementation(edc.spi.transfer) - implementation(edc.spi.policy) - implementation(edc.spi.dataplane.dataplane) - implementation(edc.dpf.util) - implementation(edc.dpf.core) - implementation(edc.policy.engine) - implementation(libs.bouncyCastle.bcpkix) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transfer) + implementation(libs.edc.spi.policy) + implementation(libs.edc.spi.dataplane.dataplane) + implementation(libs.edc.dpf.util) + implementation(libs.edc.dpf.core) + implementation(libs.edc.policy.engine) + implementation(libs.bouncyCastle.bcpkixJdk18on) implementation(libs.apache.sshd.core) implementation(libs.apache.sshd.sftp) testImplementation(libs.awaitility) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) diff --git a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java index e819229f0..f4a68f06f 100644 --- a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java +++ b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSink.java @@ -14,28 +14,29 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.io.IOException; -import java.util.List; import lombok.Builder; import lombok.NonNull; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; import org.eclipse.edc.connector.dataplane.util.sink.ParallelSink; -import org.eclipse.edc.spi.response.ResponseStatus; -import org.eclipse.edc.spi.response.StatusResult; + +import java.io.IOException; +import java.util.List; @Builder public class SftpDataSink extends ParallelSink { - @NonNull private final SftpClientWrapper sftpClientWrapper; + @NonNull + private final SftpClientWrapper sftpClientWrapper; - @Override - protected StatusResult transferParts(List parts) { - for (DataSource.Part part : parts) { - try { - sftpClientWrapper.uploadFile(part.openStream()); - } catch (IOException e) { - return StatusResult.failure(ResponseStatus.FATAL_ERROR, e.getMessage()); - } + @Override + protected StreamResult transferParts(List parts) { + for (DataSource.Part part : parts) { + try { + sftpClientWrapper.uploadFile(part.openStream()); + } catch (IOException e) { + return StreamResult.error(e.getMessage()); + } + } + return StreamResult.success(); } - return StatusResult.success(); - } } diff --git a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java index 334538fea..9020fb905 100644 --- a/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java +++ b/edc-extensions/transferprocess-sftp-client/src/main/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSource.java @@ -14,18 +14,21 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.util.stream.Stream; import lombok.Builder; import lombok.NonNull; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; +import org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult; + +import java.util.stream.Stream; @Builder public class SftpDataSource implements DataSource { - @NonNull private final SftpClientWrapper sftpClientWrapper; + @NonNull + private final SftpClientWrapper sftpClientWrapper; - @Override - public Stream openPartStream() { - Part sftpPart = new SftpPart(sftpClientWrapper); - return Stream.of(sftpPart); - } + @Override + public StreamResult> openPartStream() { + Part sftpPart = new SftpPart(sftpClientWrapper); + return StreamResult.success(Stream.of(sftpPart)); + } } diff --git a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java index 7288c6442..100961640 100644 --- a/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java +++ b/edc-extensions/transferprocess-sftp-client/src/test/java/org/eclipse/tractusx/edc/transferprocess/sftp/client/SftpDataSourceTest.java @@ -20,10 +20,6 @@ package org.eclipse.tractusx.edc.transferprocess.sftp.client; -import java.io.ByteArrayInputStream; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.SneakyThrows; import org.apache.sshd.sftp.client.SftpClient; import org.eclipse.edc.connector.dataplane.spi.pipeline.DataSource; @@ -33,31 +29,36 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.io.ByteArrayInputStream; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + class SftpDataSourceTest { - @Test - @SneakyThrows - void openPartStream() { - final SftpUser userMock = Mockito.mock(SftpUser.class); - final SftpLocation locationMock = Mockito.mock(SftpLocation.class); - final SftpClientConfig sftpClientConfig = - SftpClientConfig.builder().sftpUser(userMock).sftpLocation(locationMock).build(); - final SftpClient sftpClientMock = Mockito.mock(SftpClient.class); - final SftpClientWrapperImpl sftpClientWrapper = - Mockito.spy(new SftpClientWrapperImpl(sftpClientConfig, sftpClientMock)); - SftpDataSource sftpDataSource = Mockito.spy(new SftpDataSource(sftpClientWrapper)); - byte[] expected = new byte[] {0, 1, 2, 3}; - ByteArrayInputStream outputStream = new ByteArrayInputStream(expected); + @Test + @SneakyThrows + void openPartStream() { + final SftpUser userMock = Mockito.mock(SftpUser.class); + final SftpLocation locationMock = Mockito.mock(SftpLocation.class); + final SftpClientConfig sftpClientConfig = + SftpClientConfig.builder().sftpUser(userMock).sftpLocation(locationMock).build(); + final SftpClient sftpClientMock = Mockito.mock(SftpClient.class); + final SftpClientWrapperImpl sftpClientWrapper = + Mockito.spy(new SftpClientWrapperImpl(sftpClientConfig, sftpClientMock)); + SftpDataSource sftpDataSource = Mockito.spy(new SftpDataSource(sftpClientWrapper)); + byte[] expected = new byte[]{ 0, 1, 2, 3 }; + ByteArrayInputStream outputStream = new ByteArrayInputStream(expected); - Mockito.when(locationMock.getPath()).thenReturn("path"); - Mockito.when( - sftpClientMock.read(Mockito.anyString(), Mockito.anyInt(), Mockito.anyCollection())) - .thenReturn(outputStream); + Mockito.when(locationMock.getPath()).thenReturn("path"); + Mockito.when( + sftpClientMock.read(Mockito.anyString(), Mockito.anyInt(), Mockito.anyCollection())) + .thenReturn(outputStream); - Stream partStream = sftpDataSource.openPartStream(); - DataSource.Part part = partStream.collect(Collectors.toList()).get(0); + Stream partStream = sftpDataSource.openPartStream().getContent(); + DataSource.Part part = partStream.collect(Collectors.toList()).get(0); - Assertions.assertArrayEquals(expected, part.openStream().readAllBytes()); - Mockito.verify(sftpClientMock, Mockito.times(1)) - .read("path", 4096, List.of(SftpClient.OpenMode.Read)); - } + Assertions.assertArrayEquals(expected, part.openStream().readAllBytes()); + Mockito.verify(sftpClientMock, Mockito.times(1)) + .read("path", 4096, List.of(SftpClient.OpenMode.Read)); + } } diff --git a/edc-extensions/transferprocess-sftp-common/build.gradle.kts b/edc-extensions/transferprocess-sftp-common/build.gradle.kts index b102c23c1..66a63d9ee 100644 --- a/edc-extensions/transferprocess-sftp-common/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-common/build.gradle.kts @@ -23,8 +23,8 @@ plugins { } dependencies { - implementation(edc.spi.core) - testImplementation(edc.junit) + implementation(libs.edc.spi.core) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) diff --git a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts index bf239f371..b413b0951 100644 --- a/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts +++ b/edc-extensions/transferprocess-sftp-provisioner/build.gradle.kts @@ -25,11 +25,11 @@ plugins { dependencies { implementation(project(":edc-extensions:transferprocess-sftp-common")) - implementation(edc.spi.core) - implementation(edc.policy.engine) - implementation(edc.spi.transfer) + implementation(libs.edc.spi.core) + implementation(libs.edc.policy.engine) + implementation(libs.edc.spi.transfer) - testImplementation(edc.junit) + testImplementation(libs.edc.junit) testImplementation(libs.mockito.inline) testImplementation(libs.testcontainers.junit) } diff --git a/edc-tests/cucumber/README.md b/edc-tests/cucumber/README.md deleted file mode 100644 index db14876f7..000000000 --- a/edc-tests/cucumber/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Invoke Business-Tests via Maven - -THIS MODULE IS DEPRECATED AND WILL NOT BE MAINTAINED ANYMORE. - -```shell -./gradlew :edc-tests:test -Dcucumber=true -``` - -## Test locally using Act Tool - -> "Think globally, [`act`](https://github.com/nektos/act) locally" - -```shell -act -j business-test -``` - -## Run and debug Business-Tests local within IDE - -Please refer to [run-local documentation in docs](../docs/development/Run-business-tests-local.md) diff --git a/edc-tests/cucumber/build.gradle.kts b/edc-tests/cucumber/build.gradle.kts deleted file mode 100644 index d364320b3..000000000 --- a/edc-tests/cucumber/build.gradle.kts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -plugins { - `java-library` -} - -dependencies { - implementation(project(":edc-extensions:business-partner-validation")) - implementation(project(":edc-extensions:control-plane-adapter")) - implementation(project(":edc-extensions:cx-oauth2")) - implementation(project(":edc-extensions:data-encryption")) - implementation(project(":edc-extensions:dataplane-selector-configuration")) - implementation(project(":edc-extensions:hashicorp-vault")) - implementation(project(":edc-extensions:postgresql-migration")) - implementation(project(":edc-extensions:provision-additional-headers")) - implementation(project(":edc-extensions:transferprocess-sftp-client")) - implementation(project(":edc-extensions:transferprocess-sftp-common")) - implementation(project(":edc-extensions:transferprocess-sftp-provisioner")) - - - testImplementation("com.google.code.gson:gson:2.10.1") - testImplementation("org.apache.httpcomponents:httpclient:4.5.14") - testImplementation("org.junit.platform:junit-platform-suite:1.9.3") - testImplementation("io.cucumber:cucumber-java:7.12.0") - testImplementation("io.cucumber:cucumber-junit-platform-engine:7.12.0") - testImplementation("org.slf4j:slf4j-api:2.0.7") - testImplementation(libs.restAssured) - testImplementation(libs.postgres) - testImplementation(libs.awaitility) - testImplementation(libs.aws.s3) - testImplementation(edc.spi.core) -} - -tasks.withType(Test::class) { - onlyIf { - System.getProperty("cucumber") == "true" - } -} - -// do not publish -edcBuild { - publish.set(false) -} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/.helmignore b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/.helmignore deleted file mode 100644 index 0e8a0eb36..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml deleted file mode 100644 index 0fd769a8f..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/Chart.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# - ---- -apiVersion: v2 -name: ids-daps -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.0.1 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "0.0.1" diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/README.md b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/README.md deleted file mode 100644 index f85a94889..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Omejdn DAPS - -This chart deployes an [IDS Omejdn DAPS](https://github.com/Fraunhofer-AISEC/omejdn-server). - -Two Eclipse Dataspace Connectors need to be registered at the same DAPS instance, to be able to talk to each other. Each connector is registered in the DAPS by an unique client ID and a correpsonding client certificate. - -New connectors are configured in the omejdn _values.yaml_. - -In each Eclipse Dataspace Connector configure the following properties to use the DAPS. - -```properties - edc.oauth.client.id= - - edc.oauth.provider.jwks.url="http://:4567/.well-known/jwks.json" - edc.oauth.token.url="http://:4567/token" - - edc.oauth.private.key.alias= - edc.oauth.public.key.alias= - - edc.oauth.provider.audience=idsc:IDS_CONNECTORS_ALL -``` diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl deleted file mode 100644 index ec025d4f9..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/_helpers.tpl +++ /dev/null @@ -1,64 +0,0 @@ - - -{{/* -Expand the name of the chart. -*/}} -{{- define "omejdn.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "omejdn.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "omejdn.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "omejdn.labels" -}} -helm.sh/chart: {{ include "omejdn.chart" . }} -{{ include "omejdn.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "omejdn.selectorLabels" -}} -app.kubernetes.io/name: {{ include "omejdn.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "omejdn.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "omejdn.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml deleted file mode 100644 index dc57c6055..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/configmap.yaml +++ /dev/null @@ -1,93 +0,0 @@ -# - # Copyright (c) 2023 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License, Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - # License for the specific language governing permissions and limitations - # under the License. - # - # SPDX-License-Identifier: Apache-2.0 - # - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "omejdn.fullname" . }} - labels: - {{- include "omejdn.labels" . | nindent 4 }} -data: - scope_mapping.yml: |- - --- - idsc:IDS_CONNECTOR_ATTRIBUTES_ALL: - - referringConnector - - omejdn.yml: |- - --- - host: http://ids-daps:4567/ - path_prefix: '' - bind_to: 0.0.0.0 - allow_origin: "*" - app_env: debug - openid: false - user_backend: - - yaml - user_backend_default: yaml - accept_audience: idsc:IDS_CONNECTORS_ALL - issuer: http://ids-daps:4567/ - environment: development - default_audience: - - idsc:IDS_CONNECTORS_ALL - access_token: - expiration: 3600 - algorithm: RS256 - id_token: - expiration: 3600 - algorithm: RS256 - - plugins.yml: |- - --- - plugins: - token_user_attributes: - - clients.yml: |- - --- - - client_id: data-plane-oauth2 - client_secret: supersecret - name: provision oauth2 - grant_types: - - client_credentials - token_endpoint_auth_method: client_secret_post - scope: openid -{{- range $i, $val := .Values.connectors }} - - client_id: {{ quote $val.id }} - name: {{ quote $val.name }} - token_endpoint_auth_method: private_key_jwt - grant_types: - - client_credentials - scope: - - idsc:IDS_CONNECTOR_ATTRIBUTES_ALL - attributes: - - key: idsc - value: IDS_CONNECTOR_ATTRIBUTES_ALL - - key: securityProfile - value: idsc:BASE_SECURITY_PROFILE - {{- range $key, $value := $val.attributes }} - - key: {{ $key }} - value: {{ $value }} - {{- end }} - redirect_uri: http://localhost:4200 -{{ end -}} - - -{{- range $i, $val := .Values.connectors }} - {{ $val.name }}: {{ quote $val.certificate | toString }} -{{ end -}} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml deleted file mode 100644 index 5353b8035..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/deployment.yaml +++ /dev/null @@ -1,164 +0,0 @@ -# - # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - # - # This program and the accompanying materials are made available under the - # terms of the Apache License, Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # - # Contributors: - # Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - # - # - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "omejdn.fullname" . }} - labels: - {{- include "omejdn.labels" . | nindent 4 }} -spec: - {{- if not .Values.autoscaling.enabled }} - replicas: {{ .Values.replicaCount }} - {{- end }} - selector: - matchLabels: - {{- include "omejdn.selectorLabels" . | nindent 6 }} - template: - metadata: - {{- with .Values.podAnnotations }} - annotations: - {{- toYaml . | nindent 8 }} - {{- end }} - labels: - {{- include "omejdn.selectorLabels" . | nindent 8 }} - spec: - {{- if .Values.imagePullSecret.dockerconfigjson }} - imagePullSecrets: - - name: {{ include "omejdn.fullname" . }}-imagepullsecret - {{- else }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- end }} - serviceAccountName: {{ include "omejdn.serviceAccountName" . }} - automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - initContainers: - - name: init-daps-pvc - image: alpine - command: - - "sh" - - "-c" - args: - - | - cp /opt/config/omejdn.yml /etc/daps/omejdn.yml - cp /opt/config/clients.yml /etc/daps/clients.yml - cp /opt/config/plugins.yml /etc/daps/plugins.yml - cp /opt/config/scope_mapping.yml /etc/daps/scope_mapping.yml - apk add --update openssl - openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout /etc/keys/omejdn/omejdn.key \ - -subj "/C=DE/ST=Berlin/L=Berlin/O=Tractus-X-EDC-Test, Inc./OU=DE" - volumeMounts: - - mountPath: /etc/daps - name: config-dir - - mountPath: /etc/keys/omejdn - name: omejdn-key-dir - - mountPath: /opt/config/omejdn.yml - name: omejdn-config - subPath: omejdn.yml - - mountPath: /opt/config/scope_mapping.yml - name: scope-mapping - subPath: scope_mapping.yml - - mountPath: /opt/config/clients.yml - name: clients-config - subPath: clients.yml - - mountPath: /opt/config/plugins.yml - name: plugins-config - subPath: plugins.yml - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - volumeMounts: - - mountPath: /opt/config/ - name: config-dir - - mountPath: /opt/keys/omejdn/omejdn.key - name: omejdn-key-dir - subPath: omejdn.key - - mountPath: /opt/keys/clients/ - name: client-certificates - ports: - - name: http - containerPort: 4567 - protocol: TCP - livenessProbe: - httpGet: - path: /jwks.json - port: http - readinessProbe: - httpGet: - path: /jwks.json - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - env: - - name: OMEJDN_JWT_AUD_OVERRIDE - value: "idsc:IDS_CONNECTORS_ALL" - - name: OMEJDN_PLUGINS - value: "config/plugins.yml" - volumes: - - name: config-dir - emptyDir: { } - - name: omejdn-key-dir - emptyDir: { } - - name: omejdn-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: omejdn.yml - path: omejdn.yml - - name: scope-mapping - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: scope_mapping.yml - path: scope_mapping.yml - - name: clients-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: clients.yml - path: clients.yml - - name: plugins-config - configMap: - name: {{ include "omejdn.fullname" . }} - items: - - key: plugins.yml - path: plugins.yml - - name: client-certificates - configMap: - name: {{ include "omejdn.fullname" . }} - items: - {{- range $i, $val := .Values.connectors }} - - key: {{ $val.name }} - path: {{ $val.id }}.cert - {{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml deleted file mode 100644 index cf5eb97d0..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/hpa.yaml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -{{- if .Values.autoscaling.enabled }} ---- -apiVersion: autoscaling/v2beta1 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "omejdn.fullname" . }} - labels: - {{- include "omejdn.labels" . | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "omejdn.fullname" . }} - minReplicas: {{ .Values.autoscaling.minReplicas }} - maxReplicas: {{ .Values.autoscaling.maxReplicas }} - metrics: - {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - - type: Resource - resource: - name: cpu - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml deleted file mode 100644 index 44f573e0f..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/imagepullsecret.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License, Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - # License for the specific language governing permissions and limitations - # under the License. - # - # SPDX-License-Identifier: Apache-2.0 - # - -{{- if .Values.imagePullSecret.dockerconfigjson }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "edc-dataplane.fullname" . }}-imagepullsecret - namespace: {{ .Release.Namespace | default "default" | quote }} - labels: - {{- include "edc-dataplane.labels" . | nindent 4 }} -data: - .dockerconfigjson: {{ .Values.imagePullSecret.dockerconfigjson }} -type: kubernetes.io/dockerconfigjson -{{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml deleted file mode 100644 index 947e69742..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/service.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "omejdn.fullname" . }} - labels: - {{- include "omejdn.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "omejdn.selectorLabels" . | nindent 4 }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml deleted file mode 100644 index 536f31871..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/templates/serviceaccount.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -{{- if .Values.serviceAccount.create -}} ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "omejdn.serviceAccountName" . }} - labels: - {{- include "omejdn.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml deleted file mode 100644 index f411b8774..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/omejdn/values.yaml +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - ---- -# Default values for omejdn. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -# -- Specifies how many replicas of a deployed pod shall be created during the deployment -# Note: If horizontal pod autoscaling is enabled this setting has no effect -replicaCount: 1 - -image: - # -- Which omjedn container image to use - repository: ghcr.io/fraunhofer-aisec/omejdn-server - # -- [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) to use - pullPolicy: IfNotPresent - # -- Overrides the image tag whose default is the chart appVersion - tag: "1.7.1" - -imagePullSecret: - # -- Image pull secret to create to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) - # Note: This value needs to adhere to the [(base64 encoded) .dockerconfigjson format](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials). - # Furthermore, if 'imagePullSecret.dockerconfigjson' is defined, it takes precedence over 'imagePullSecrets'. - dockerconfigjson: "" - -# -- Overrides the charts name -nameOverride: "" - -# -- Overrides the releases full name -fullnameOverride: "" - -serviceAccount: - # -- Specifies whether a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) should be created per release - create: true - # -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) to add to the service account - annotations: {} - # -- The name of the service account to use. If not set and create is true, a name is generated using the release's fullname template - name: "" - -# -- Whether to [automount kubernetes API credentials](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server) into the pod -automountServiceAccountToken: false - -# -- [Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) added to deployed [pods](https://kubernetes.io/docs/concepts/workloads/pods/) -podAnnotations: {} - -# The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment -podSecurityContext: {} - -# The [container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) defines privilege and access control settings for a Container within a pod -securityContext: {} - -service: - # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types) to expose the running application on a set of Pods as a network service. - type: ClusterIP - # -- [Service type](https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service) to expose the running application on a set of Pods as a network service. - port: 4567 - -# -- [Resource management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) applied to the deployed pod -resources: {} - -autoscaling: - # -- Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) - enabled: false - # -- Minimal replicas if resource consumption falls below resource threshholds - minReplicas: 1 - # -- Maximum replicas if resource consumption exceeds resource threshholds - maxReplicas: 100 - # -- targetAverageUtilization of cpu provided to a pod - targetCPUUtilizationPercentage: 80 - # -- targetAverageUtilization of memory provided to a pod - targetMemoryUtilizationPercentage: 80 - -# -- [Node-Selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain the Pod to nodes with specific labels. -nodeSelector: {} - -# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) are applied to Pods to schedule onto nodes with matching taints. -tolerations: [] - -# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) constrains which nodes the Pod can be scheduled on based on node labels. -affinity: {} - -# List of connector clients. Certificate and Client-ID must be configured in parallel. -#
-# Example Connector: -# - id: grMsEz3EcsS3ENYJufNgUIeg4QsaL49M0gWxSexPdC4pon96Nvju90D8RlvAJB21 -# name: my-connector -# attributes: -# issuerConnector: http://localhost:8080/ -# certificate: |- -# -----BEGIN CERTIFICATE----- -# foo -# -----END CERTIFICATE----- -connectors: [] diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.gitignore b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.gitignore deleted file mode 100644 index 8681aba50..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# ignore downloaded helm depdencies -charts/ - -Chart.lock diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.helmignore b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.helmignore deleted file mode 100644 index 8c60d7821..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/.helmignore +++ /dev/null @@ -1,24 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ -docs diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml deleted file mode 100644 index c445d4a36..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/Chart.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# - ---- -apiVersion: v2 -name: all-in-one -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" - -dependencies: - # IDS Dynamic Attribute Provisioning Service (IAM) - - name: ids-daps - version: 0.0.1 - repository: "file://../omejdn" - alias: idsdaps - condition: install.daps - - # HashiCorp Vault - - name: vault - alias: vault - version: 0.20.0 - repository: https://helm.releases.hashicorp.com - condition: install.vault - - # PostgreSQL - - name: postgresql - alias: plato-postgresql - version: 12.1.6 - repository: https://charts.bitnami.com/bitnami - condition: install.postgresql - - - name: postgresql - alias: sokrates-postgresql - version: 12.1.6 - repository: https://charts.bitnami.com/bitnami - condition: install.postgresql - - # MinIo - - name: minio - alias: minio - repository: https://charts.min.io - version: 4.1.0 - condition: install.minio diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/README.md b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/README.md deleted file mode 100644 index 89cadb1aa..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# Supporting Infrastructure Deployment - -The Supporting Infrastructure Deployment creates a complete, independent and already configured EDC test environment. -During the automated business tests, these infrastructure components are deployed together with two connectors (Plato & Sokrates). - -This deployment could also be used as - -- reference setup for teams, that want to create their own connector -- standalone infrastructure to try things out - -This deployment should **never** be used - -- in **any** production or near production environments -- in **any** long living internet facing connector setups - -## Components - -Overview of the installed components. - -![Deployed Components](diagrams/deployed_components.png) - -### Omejdn DAPS - -The Dynamic Attribute Provisioning Service (DAPS) is a component of the IDS Ecosystem. -The Fraunhofer Institute has created a DAPS reference implementation, the Omejdn -DAPS ([link](https://github.com/Fraunhofer-AISEC/omejdn-server)). This deplyoment configures and deployes a instance of -this reference implementation. - -Definition of DAPS from the IDS Reference architecture v3.0 -> The Identity Provider acts as an agent for the International -> Data Spaces Association. It is responsible for issuing technical identities to parties that have been approved to become -> Participants in the International Data Spaces. The Identity -> Provider is instructed to issue identities based on approved -> roles (e.g., App Store or App Provider). Only if equipped with -> such an identity, an entity is allowed to participate in the International Data Spaces - -Also, please note, that the Omejdn DAPS is meant as research sandbox and should not be used in anq -productive environment. - -> **IMPORTANT:** Omejdn is meant to be a research sandbox in which we can (re)implement standard protocols and -> potentially extend and modify functionality under the hood to support research projects. Use at your own -> risk! ([source](https://github.com/Fraunhofer-AISEC/omejdn-server)) - -### HashiCorp Vault - -The Control- and Data Plane persist confidential in the vault and persist and communicate using only the secret -names. Hence, it is not possible to run a connector without an instance of a vault. - -### Backend Application - -After a Data Transfer is successfully prepared the control plane will contact the a configurable endpoint with the -information it needs to initiate the data transfer. This transfer flow, where something like a Backend Application is -required, is unique to the HTTP Proxy data transfer flow. - -The Backend Application has an API endpoint, that is configured in the control plane. After it gets called with the data -transfer information, it will do the actual data transfer and store the data on disk. - -### PostgreSQL - -This database is used to persist the state of the Control Plane. - -## Setup - -Follow these steps to get a fully functional EDC demo environment out of the box. - -### Requirements - -Install on your machine: - -- Minikube - - Documentation -- Helm - - Documentation - -## Start Demo Environment - -Update Helm Dependencies: - -```bash -helm dependency update -``` - -Install Demo Chart: - -```bash -helm install tx-infrastructure --namespace tx --create-namespace . -``` - -## Stop Demo Environment - -Uninstall Demo Chart: - -```bash -helm uninstall tx-infrastructure --namespace tx -``` diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.png b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.png deleted file mode 100644 index 443b739f87a11330b5837a2bdd3a33f85d30904e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22169 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 diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.puml b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.puml deleted file mode 100644 index 9f3dfdd94..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/diagrams/deployed_components.puml +++ /dev/null @@ -1,20 +0,0 @@ -@startuml - -node Infrastructure as "Tractus-X Connector\nSupporting Infrastructure" { - node SokratesSetup as "Persistence / PostgreSQL" { - database SokratesPsql as "Sokrates PSQL" - database PlatoPsql as "Plato PSQL" - } - node SharedComponents as "Additional Components" { - component Vault as "HashiCorp Vault" - component BackendService as "Backend Application" - } - node IdentityProvider as "Identity Provider" { - component OmejdnDaps as "Omejdn DAPS" - } -} - -IdentityProvider -[hidden]down- SharedComponents -IdentityProvider -[hidden]right- SokratesSetup - -@enduml diff --git a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml b/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml deleted file mode 100644 index 287fc9975..000000000 --- a/edc-tests/cucumber/src/main/resources/deployment/helm/supporting-infrastructure/values.yaml +++ /dev/null @@ -1,309 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# - ---- - -########### -# Install # -########### -install: - daps: true - postgresql: true - vault: true - minio: true - - -######## -# DAPS # -######## -idsdaps: - fullnameOverride: "ids-daps" - connectors: - - id: E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65:keyid:E7:07:2D:74:56:66:31:F0:7B:10:EA:B6:03:06:4C:23:7F:ED:A6:65 - name: sokrates - attributes: - referringConnector: http://sokrates-controlplane/BPNSOKRATES - # Must be the same certificate that is stores in section 'sokrates-vault' - certificate: |- - -----BEGIN CERTIFICATE----- - MIIEAzCCAuugAwIBAgIULy0aTdGiGkyvVp7l5Ccoq7DQREgwDQYJKoZIhvcNAQEL - BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl - cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ - TjEyMzQwHhcNMjMwNDI2MTI1OTE5WhcNMzMwNDIzMTI1OTE5WjCBkDELMAkGA1UE - BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK - DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD - DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ - KoZIhvcNAQEBBQADggEPADCCAQoCggEBALCr0h3vT5kNnwWhAmGRvEEo38nKyiXB - Gx0GlepYToKklMgtGIX25OkOrXJqq4BzybxN27DoWvU9DEixylkCbhwmwmpI3IhF - 8w6cV/odaYdQ3tEeZ6zWYXqKx+MVWTHQ8A4Njy64PWNDWBZmaGvxeE48i7EJnnrM - M5CGDAKbA/Jd1nlFxaq9hGiaCHa2kCNKdfrJ6ZUda5rPlLJk5at3VPxvRIpT50Gp - 3P4PtdwpwIHwa7y1xTBc43bEfcD1lmR9VkkxCX8lg4V1OBLx1GVwoUZBkN8P4POT - t+gQq7FbDbBEeOSmKELC3Tc8D8JCGv94sEg6o+4yzgpvyIvMyV8uGcECAwEAAaNT - MFEwHQYDVR0OBBYEFIxEsuJTl+5V8vTCUhMGhWsmdQShMB8GA1UdIwQYMBaAFIxE - suJTl+5V8vTCUhMGhWsmdQShMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL - BQADggEBAJHOKmFNZDk5xebzBARgcIrYrmRb5pIU4gNCWh/q1TF0+CFnnK8RTFTZ - 12pTbid6v5knn/f9bsilnudhxBzBQ4bukiI8Be0nzYfZU2dTU+w1cl/JnJfkGirt - 8Nwqv3fiUXfFBl8nE0RduAk9XF/UBIZXPapE6u1zR29jvuV+ppmhQrFFeJufeBGd - Wwn6XGK4fzENGDyjdk4QB/dg3/heM5h330vIGO4hVvlQBfJhNbC7Iikkr5ulytfd - deuZIfa7hG6WgIgGhg3YL1p/TTpJamBDS860PWyI7RH3o53VPphu/y2Rpud5AECV - xcrqaSGUTZPVyTUB8BxE32LqFDbpZb4= - -----END CERTIFICATE----- - - id: 99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23:keyid:99:83:A7:17:86:FF:98:93:CE:A0:DD:A1:F1:36:FA:F6:0F:75:0A:23 - name: plato - attributes: - referringConnector: http://plato-controlplane/BPNPLATO - # Must be the same certificate that is stores in section 'plato-vault' - certificate: |- - -----BEGIN CERTIFICATE----- - MIID7TCCAtWgAwIBAgIUJv9K1yHIGf/crrkLHtKlYUd06OwwDQYJKoZIhvcNAQEL - BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl - cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIz - MDQyNjEyNTQxNFoXDTMzMDQyMzEyNTQxNFowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD - VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD - VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj - LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC - AQEAwajlhy7Uf6V5P7UNmb5fSL0uJLt1EIeqEuLMTc8J6VhAgv7WGtRJiZySIFfQ - VBcwizO0C+pzyHXY957HJKFWhuiiACmO1NBOQuF5TCe/X9MHUOfK1l52mDu6zXhV - pPRn8ZY7CBul/94Wb/SS0+SJ6ogkdB8nwI++3ET166Wuv1aSdGKAc2daseQ1Ynau - fPL2ziVQDfPh51+9VUG8tuwGrwagFrawDk5FkZB7Z+nPZ0WpmLN2Q+oVRFaSgDRu - cTx6ejMs2tMSKzJIJjIVzgHRIULyPSMS3AlwHub3FwZp2TrXolczDJWL+XIbfHfw - 6KKNWGR80/RM457AhNzApd0GYQIDAQABo1MwUTAdBgNVHQ4EFgQU4TU1BUk881qd - H+g1I2jAuL+jAyAwHwYDVR0jBBgwFoAU4TU1BUk881qdH+g1I2jAuL+jAyAwDwYD - VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEOkReVM7644reZoPIWEP - 4XVyXKwoIk/zxVPedtcCoXWdG2zVXULvjIc0vf65Ih1e9b65rz+v6yn0isZ5zfSm - mFOsVMg0vL9I4QQtOSx9tL5MXq+zPeRLVpeRvvUD67+wKkf9n+e1DByqCVfaF68U - DDq0L0VQgp3fRNEzLjcXlOOIQ4W/qc1lnxoxVCzQuLJwkZejokV9cj5JDBojmAuK - g+IDL1aArzzKMD5iAAqm0rDbDnMhn0Km+AshDEWgAqtnsVEBRlt+GDAc+d0nplLY - BR8UsaLdtAVHaivXCaZGpjiOsvdOpTwWOaU9HOkuf/1uX5DTaxtClt2BXAnxF3Ug - iQ== - -----END CERTIFICATE----- - -############## -# PostgreSQL # -############## -plato-postgresql: - fullnameOverride: "plato-postgresql" - primary: - persistence: - enabled: false - readReplicas: - persistence: - enabled: false - auth: - database: "edc" - username: "user" - password: "password" -sokrates-postgresql: - fullnameOverride: "sokrates-postgresql" - primary: - persistence: - enabled: false - readReplicas: - persistence: - enabled: false - auth: - database: "edc" - username: "user" - password: "password" - -######### -# MINIO # -######### -minio: - fullnameOverride: minio - replicas: 2 - drivesPerNode: 0 - serviceAccount: - create: false - persistence: - size: 128Mi - resources: - requests: - memory: 128Mi - service: - type: NodePort - control: - port: 9000 - users: - - accessKey: platoqwerty123 - secretKey: platoqwerty123 - policy: customBucketPolicy - - accessKey: sokratesqwerty123 - secretKey: sokratesqwerty123 - policy: customBucketPolicy - buckets: - # in some cases the minio API acts strange if there exists no bucket at all - - name: dummybucket - policy: none - purge: true - policies: - - name: customBucketPolicy - statements: - - resources: - - 'arn:aws:s3:::*' - actions: - - "s3:PutObject" - - "s3:ListBucket" - - "s3:CreateBucket" - - "s3:GetObject" - - "s3:DeleteObject" - - "s3:DeleteBucket" - -######### -# VAULT # -######### -vault: - fullnameOverride: "vault" - injector: - enabled: false - server: - dev: - enabled: true - devRootToken: "root" - # Must be the same certificate that is configured in section 'ids-daps' - postStart: - - "sh" - - "-c" - - | - { - - sleep 5 - - /bin/vault kv put secret/plato/data-encryption-aes-keys content=H7j47H6vVQQOv/hbdAYz+w== - - cat << EOF | /bin/vault kv put secret/plato/daps/my-plato-daps-key content=- - -----BEGIN PRIVATE KEY----- - MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDBqOWHLtR/pXk/ - tQ2Zvl9IvS4ku3UQh6oS4sxNzwnpWECC/tYa1EmJnJIgV9BUFzCLM7QL6nPIddj3 - nsckoVaG6KIAKY7U0E5C4XlMJ79f0wdQ58rWXnaYO7rNeFWk9GfxljsIG6X/3hZv - 9JLT5InqiCR0HyfAj77cRPXrpa6/VpJ0YoBzZ1qx5DVidq588vbOJVAN8+HnX71V - Qby27AavBqAWtrAOTkWRkHtn6c9nRamYs3ZD6hVEVpKANG5xPHp6Myza0xIrMkgm - MhXOAdEhQvI9IxLcCXAe5vcXBmnZOteiVzMMlYv5cht8d/Dooo1YZHzT9EzjnsCE - 3MCl3QZhAgMBAAECggEBAKR/RphRWwciE5/dtrPFVUKAD1X8NS/ZTMnGBCyDlLO0 - 1vduZ4dakyxk5mq6rKcBG6biQClu+PJpx+Zt5FJlCQ6HRDRHGKAEYLXGuDXL/W7z - 3d8HRPBaRPqCoeYuNPFs+W3oYjQ86AAzMXPfl2iNU+j3w58vZ6DVeRW5LfsAPTMg - Z8Sooa1jD2a/7uDN4lC0FGkTWif//Dio5tbijqeG8xqBnS8iKi4hgxcQA9azd0KB - 6uwvbX/izq4sVR2ZjxtT9WPX1cpOcXjUZBM9px9eAwLPmsM/AUAOHkKkd1DPYLjX - yyB0qvz+LmUQdJv11yGagsW7lrrvsBsro4ZMp0Ot1wECgYEA5j2XFCKNUcX/8OFm - 8E9q6DXyrd9T3rMxPYWR9nRwV0upN9Zd9mnvOKnl5MYQSgP0XJgwwyHawmG3wIcx - 0puf3uWi2lSpt6aafMCW6JEJbK/49XSPAjrptwkZUcCT3XJv1tMZuXzhv/p4t24o - hw9/EtzVxK7thGGZD6sDsQtbOlkCgYEA11OUofuD1VWN5YwFciPv0RhyfDyYYK7e - nPMXEoiBMQJnGkp3eaUzUgej/V93VtJcg9h0Tqn6NpI4rWUfUdi5ihZ6+hcvUIO4 - Roh+Oxpmu0yBfuBo7Uwf5XMpoQu74Z+cr24Pv32YtEUshUZidMuvOMaBXNJGlKiG - DjbCUV0CG0kCgYAVHvlJA5JrOfqsokDLMr3f53MHuED9YPrXZfVp4myb1XkEgknE - XRtw20UXo4PDBnHYPK3ceLKUuloc80oCw/v6ep5h4PpguovZfeFaHFP9AHeaLMMh - tT3TaKZF9aCa4/CWiG8HsQkUj2mbiiN1oFpL5K5HiLSJPFrKMSn5h80qoQKBgQC2 - obt1UEDXFwONaJ/N2dE0RkoEOdj8WBWUhVJSc9kv2lvcnsCLOqU2tChRZUFxMGcr - pNGxTtZcptTPrO9NmkZ0avDPYg7NeYs4t9hpBNGRlyhWlrwoWOLM2Eq8v5kRmzFo - Ui+lOT/l1q4WNEaZzZDG1Qcv1WHsAKwDLkrOe9ankQKBgQDM1fdqraKN2lCkDSPU - /Uw5nmFA7gNJQ9ta6CVITlDMWFb+e2OcDK7pKT1iEhJAfGndtQ0lwK6I5VDDxhXY - DGcU2UIWMAiJOZILDVjkny9brrIQ/fTwZps2qWNJ0bmYsmwPCe9QskNWz8sYAY6p - eBB+WUqNNqBb25p2CmcwqoT7Tg== - -----END PRIVATE KEY----- - EOF - - cat << EOF | /bin/vault kv put secret/plato/daps/my-plato-daps-crt content=- - -----BEGIN CERTIFICATE----- - MIID7TCCAtWgAwIBAgIUJv9K1yHIGf/crrkLHtKlYUd06OwwDQYJKoZIhvcNAQEL - BQAwgYUxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl - cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRjLmRlbW8uY2F0ZW5hLXgubmV0MB4XDTIz - MDQyNjEyNTQxNFoXDTMzMDQyMzEyNTQxNFowgYUxCzAJBgNVBAYTAkRFMQ8wDQYD - VQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEMMAoGA1UECgwDQk1XMSAwHgYD - VQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0bmVyMjEkMCIGA1UEAwwbcGxhdG8tZWRj - LmRlbW8uY2F0ZW5hLXgubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC - AQEAwajlhy7Uf6V5P7UNmb5fSL0uJLt1EIeqEuLMTc8J6VhAgv7WGtRJiZySIFfQ - VBcwizO0C+pzyHXY957HJKFWhuiiACmO1NBOQuF5TCe/X9MHUOfK1l52mDu6zXhV - pPRn8ZY7CBul/94Wb/SS0+SJ6ogkdB8nwI++3ET166Wuv1aSdGKAc2daseQ1Ynau - fPL2ziVQDfPh51+9VUG8tuwGrwagFrawDk5FkZB7Z+nPZ0WpmLN2Q+oVRFaSgDRu - cTx6ejMs2tMSKzJIJjIVzgHRIULyPSMS3AlwHub3FwZp2TrXolczDJWL+XIbfHfw - 6KKNWGR80/RM457AhNzApd0GYQIDAQABo1MwUTAdBgNVHQ4EFgQU4TU1BUk881qd - H+g1I2jAuL+jAyAwHwYDVR0jBBgwFoAU4TU1BUk881qdH+g1I2jAuL+jAyAwDwYD - VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEOkReVM7644reZoPIWEP - 4XVyXKwoIk/zxVPedtcCoXWdG2zVXULvjIc0vf65Ih1e9b65rz+v6yn0isZ5zfSm - mFOsVMg0vL9I4QQtOSx9tL5MXq+zPeRLVpeRvvUD67+wKkf9n+e1DByqCVfaF68U - DDq0L0VQgp3fRNEzLjcXlOOIQ4W/qc1lnxoxVCzQuLJwkZejokV9cj5JDBojmAuK - g+IDL1aArzzKMD5iAAqm0rDbDnMhn0Km+AshDEWgAqtnsVEBRlt+GDAc+d0nplLY - BR8UsaLdtAVHaivXCaZGpjiOsvdOpTwWOaU9HOkuf/1uX5DTaxtClt2BXAnxF3Ug - iQ== - -----END CERTIFICATE----- - EOF - - /bin/vault kv put secret/sokrates/data-encryption-aes-keys content=OcvxzWCK8ETSjt1jmZw3RA== - - cat << EOF | /bin/vault kv put secret/sokrates/daps/my-sokrates-daps-key content=- - -----BEGIN PRIVATE KEY----- - MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwq9Id70+ZDZ8F - oQJhkbxBKN/JysolwRsdBpXqWE6CpJTILRiF9uTpDq1yaquAc8m8Tduw6Fr1PQxI - scpZAm4cJsJqSNyIRfMOnFf6HWmHUN7RHmes1mF6isfjFVkx0PAODY8uuD1jQ1gW - Zmhr8XhOPIuxCZ56zDOQhgwCmwPyXdZ5RcWqvYRomgh2tpAjSnX6yemVHWuaz5Sy - ZOWrd1T8b0SKU+dBqdz+D7XcKcCB8Gu8tcUwXON2xH3A9ZZkfVZJMQl/JYOFdTgS - 8dRlcKFGQZDfD+Dzk7foEKuxWw2wRHjkpihCwt03PA/CQhr/eLBIOqPuMs4Kb8iL - zMlfLhnBAgMBAAECggEAVYco1mMXRsIoXQJAc9moqGbQSBGLYVGl/ZxFkUik4Wwp - turV92y6DvWTFFP9qNblL+sFUxR5jEW8n6iqjAK4KZq9/dQ+Jx6t90HK+YOppd+J - rvUoPa0fTcLH1/Bq2MoMnNEFoxmAZoCgsV9sZ+1jT4TSH6fHeC1JPUsXn19KPtdO - 0b0XvRCVU4sPpzXeaRypnwTsDMgHUoGvxoHQ7Pif5iTnEdgvc7V3ACWOanp/bEuM - 9hoHquggrO/F8SDC4wjn4BlwsxedQZyVF4a76iGS3D/CFrYd8cUKJyCtGySEl7jS - kIwDoG4oQV5mLFSLaq1BDOo8W5ku9JXAW2DZiEgkPQKBgQDav9QTSOp+gqfCDMhT - c45wxYfLfR5QCS5BLufdMmlocL/DzTHTsVddGnOGLoDr8Dbm9nL/vcPmgRtZ0crD - aGqn7sgmbpN6jMsnXhGuOhPZt7Folfbkhv6EFfyjeTZdY4vacrINp97rdVbRvNLs - pDJiHE7PjDTCJh8q2gcWqgc+vwKBgQDOwaJ8NvnnUwrBGzkUHdM4bGunxlK3VV0s - r1BFkmLXbF0qr8sTaUtBj3rbfvMe9R/5hGcuAyDWo+MyVoHc0nzkU1jQwqObLUZB - kg9ZJj4qmnGKnd39TfhDoEluBnl+iYhbXav/2F5eB2UpR0c+DHPrsreGdI3s7O9O - aLL+x5FHfwKBgQCNDwhdyzZTkENHkeCYV7rxo58WrD8g01q9c9bWv8xTKemvBKHt - 5bz1b7oxO8ms24E73I55tdAe0wBlIjDDY5Dra8IrbkCx1Rqn7zQtiowEaD0BuTq1 - UQvM9zSr4d0ZybiEjFOfFLJeWZM7uqy1JojK1YBIvBvFWrnccy4BAnGblwKBgQC5 - GHLNfy4koJw9GpDz6GuC1NVgAtVkWaCrc1uKnS2tq86Qe4ZzH02HKNsVC8a9jTcN - 2zG/6H8KiPfJxdZGiY3TnqYhZk6vik2eQBNLfUgkPdWuAfyNW7MJX8K9JEC6Pof7 - O5XS2rJIvZgb5zrpWp6ggIN6dHfmhosKiALOwnzWIwKBgBsvToOVMzL/7IL4VtSj - u9P+g2shtg9w1dpnscHxUVi2fbKeRRmv2AT140lVpSznIPUmw6FVFUhqE1OTnu1c - qS53otAdwiHAfmYz8u0dZeO0Hc5g4K9geB/BsSthXo3u10HuIVLxefKq2M+3zfJj - ZvNovy5dPYu82VTfm3gUX+Ca - -----END PRIVATE KEY----- - EOF - - cat << EOF | /bin/vault kv put secret/sokrates/daps/my-sokrates-daps-crt content=- - -----BEGIN CERTIFICATE----- - MIIEAzCCAuugAwIBAgIULy0aTdGiGkyvVp7l5Ccoq7DQREgwDQYJKoZIhvcNAQEL - BQAwgZAxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl - cmxpbjEMMAoGA1UECgwDQk1XMSAwHgYDVQQLDBdlZGMtcGxheWdyb3VuZC1wYXJ0 - bmVyMTEvMC0GA1UEAwwmc29rcmF0ZXMtZWRjLmRlbW8uY2F0ZW5hLXgubmV0L0JQ - TjEyMzQwHhcNMjMwNDI2MTI1OTE5WhcNMzMwNDIzMTI1OTE5WjCBkDELMAkGA1UE - BhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVybGluMQwwCgYDVQQK - DANCTVcxIDAeBgNVBAsMF2VkYy1wbGF5Z3JvdW5kLXBhcnRuZXIxMS8wLQYDVQQD - DCZzb2tyYXRlcy1lZGMuZGVtby5jYXRlbmEteC5uZXQvQlBOMTIzNDCCASIwDQYJ - KoZIhvcNAQEBBQADggEPADCCAQoCggEBALCr0h3vT5kNnwWhAmGRvEEo38nKyiXB - Gx0GlepYToKklMgtGIX25OkOrXJqq4BzybxN27DoWvU9DEixylkCbhwmwmpI3IhF - 8w6cV/odaYdQ3tEeZ6zWYXqKx+MVWTHQ8A4Njy64PWNDWBZmaGvxeE48i7EJnnrM - M5CGDAKbA/Jd1nlFxaq9hGiaCHa2kCNKdfrJ6ZUda5rPlLJk5at3VPxvRIpT50Gp - 3P4PtdwpwIHwa7y1xTBc43bEfcD1lmR9VkkxCX8lg4V1OBLx1GVwoUZBkN8P4POT - t+gQq7FbDbBEeOSmKELC3Tc8D8JCGv94sEg6o+4yzgpvyIvMyV8uGcECAwEAAaNT - MFEwHQYDVR0OBBYEFIxEsuJTl+5V8vTCUhMGhWsmdQShMB8GA1UdIwQYMBaAFIxE - suJTl+5V8vTCUhMGhWsmdQShMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL - BQADggEBAJHOKmFNZDk5xebzBARgcIrYrmRb5pIU4gNCWh/q1TF0+CFnnK8RTFTZ - 12pTbid6v5knn/f9bsilnudhxBzBQ4bukiI8Be0nzYfZU2dTU+w1cl/JnJfkGirt - 8Nwqv3fiUXfFBl8nE0RduAk9XF/UBIZXPapE6u1zR29jvuV+ppmhQrFFeJufeBGd - Wwn6XGK4fzENGDyjdk4QB/dg3/heM5h330vIGO4hVvlQBfJhNbC7Iikkr5ulytfd - deuZIfa7hG6WgIgGhg3YL1p/TTpJamBDS860PWyI7RH3o53VPphu/y2Rpud5AECV - xcrqaSGUTZPVyTUB8BxE32LqFDbpZb4= - -----END CERTIFICATE----- - EOF - } - - ui: - enabled: true - externalPort: 8200 - targetPort: 8200 diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/AssetStepDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/AssetStepDefs.java deleted file mode 100644 index 0405bf646..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/AssetStepDefs.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.eclipse.tractusx.edc.tests.data.Asset; -import org.eclipse.tractusx.edc.tests.data.NullDataAddress; - -public class AssetStepDefs { - - @Given("'{connector}' has the following assets") - public void hasAssets(Connector connector, DataTable table) throws Exception { - final DataManagementAPI api = connector.getDataManagementAPI(); - final List assets = parseDataTable(table); - - for (Asset asset : assets) api.createAsset(asset); - } - - @Given("'{connector}' has '{int}' assets") - public void hasAssets(Connector connector, int assetCount) throws Exception { - final DataManagementAPI api = connector.getDataManagementAPI(); - - for (var i = 0; i < assetCount; i++) - api.createAsset( - new Asset( - UUID.randomUUID().toString(), i + 1 + " / " + assetCount, NullDataAddress.INSTANCE)); - } - - private List parseDataTable(DataTable table) { - final List assets = new ArrayList<>(); - - for (Map map : table.asMaps()) { - String id = map.get("id"); - String description = map.get("description"); - assets.add(new Asset(id, description, NullDataAddress.INSTANCE)); - } - - return assets; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java deleted file mode 100644 index 21beba150..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendDataService.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.tests; - - -import java.io.InputStream; -import java.util.List; - -public interface BackendDataService { - List list(String path); - - boolean exists(String path); - - byte[] get(String path); - - void post(String path, InputStream inputStream, long length); - - void post(String path, InputStream inputStream); - - void post(String path, byte[] content); - - void delete(String path); -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java deleted file mode 100644 index f6773d7c0..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/BackendServiceSteps.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.java.en.Given; - -public class BackendServiceSteps { - - @Given("'{connector}' has an empty backend-service") - public void cleanBackendService(Connector connector) { - var backendServiceBackendAPI = connector.getBackendServiceBackendAPI(); - - backendServiceBackendAPI.list("/").forEach(backendServiceBackendAPI::delete); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/CatalogStepDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/CatalogStepDefs.java deleted file mode 100644 index 3bb8e9c5a..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/CatalogStepDefs.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import org.eclipse.tractusx.edc.tests.data.ContractOffer; -import org.junit.jupiter.api.Assertions; - -public class CatalogStepDefs { - - private List lastRequestedOffers; - - @When("'{connector}' requests the catalog from '{connector}'") - public void requestCatalog(Connector sender, Connector receiver) throws IOException { - - final DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - final String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - - lastRequestedOffers = dataManagementAPI.requestCatalogFrom(receiverIdsUrl); - } - - @Then("the catalog contains the following offers") - public void verifyCatalogContains(DataTable table) { - for (Map map : table.asMaps()) { - final String sourceContractDefinitionId = map.get("source definition"); - final String assetId = map.get("asset"); - - final boolean isInCatalog = isInCatalog(assetId, sourceContractDefinitionId); - - Assertions.assertTrue( - isInCatalog, - String.format( - "Expected the catalog to contain offer for definition '%s' and asset '%s' ", - sourceContractDefinitionId, assetId)); - } - } - - @Then("the catalog does not contain the following offers") - public void verifyCatalogContainsNot(DataTable table) { - for (Map map : table.asMaps()) { - final String sourceContractDefinitionId = map.get("source definition"); - final String assetId = map.get("asset"); - - final boolean isInCatalog = isInCatalog(assetId, sourceContractDefinitionId); - - Assertions.assertFalse( - isInCatalog, - String.format( - "Expected the catalog to not contain offer for definition '%s' and asset '%s' ", - sourceContractDefinitionId, assetId)); - } - } - - @Then("the catalog contains '{int}' offers") - public void verifyCatalogContainsXOffers(int offerCount) { - - Assertions.assertEquals( - offerCount, - lastRequestedOffers.size(), - String.format( - "Expected the catalog to contain '%s' offers, but got '%s'.", - offerCount, lastRequestedOffers.size())); - } - - private boolean isInCatalog(String assetId, String definitionId) { - return lastRequestedOffers.stream() - .anyMatch(c -> c.getAssetId().equals(assetId) && c.getId().startsWith(definitionId)); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java deleted file mode 100644 index d4e2ea7a8..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Connector.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - - -import org.eclipse.tractusx.edc.tests.util.DatabaseCleaner; -import org.eclipse.tractusx.edc.tests.util.S3Client; - -import static org.mockito.Mockito.mock; - -public class Connector { - - private final String name; - - private final Environment environment; - - private final DataManagementAPI dataManagementAPI; - - private final DatabaseCleaner databaseCleaner; - - - private final S3Client s3Client; - - public Connector(String name, Environment environment) { - this.name = name; - this.environment = environment; - dataManagementAPI = loadDataManagementAPI(); - databaseCleaner = loadDatabaseCleaner(); - s3Client = createS3Client(); - } - - public BackendDataService getBackendServiceBackendAPI() { - return mock(BackendDataService.class); - } - - public DatabaseCleaner getDatabaseCleaner() { - return databaseCleaner; - } - - public DataManagementAPI getDataManagementAPI() { - return dataManagementAPI; - } - - public Environment getEnvironment() { - return environment; - } - - public S3Client getS3Client() { - return s3Client; - } - - public String getName() { - return name; - } - - private DataManagementAPI loadDataManagementAPI() { - return new DataManagementAPI( - environment.getDataManagementUrl(), environment.getDataManagementAuthKey()); - } - - private DatabaseCleaner loadDatabaseCleaner() { - return new DatabaseCleaner( - environment.getDatabaseUrl(), - environment.getDatabaseUser(), - environment.getDatabasePassword()); - } - - - private S3Client createS3Client() { - return new S3Client(environment); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java deleted file mode 100644 index 364e266f3..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - - -public class ConnectorFactory { - private static final Map CONNECTOR_CACHE = new ConcurrentHashMap<>(); - - public static Connector byName(String name) { - Objects.requireNonNull(name); - return CONNECTOR_CACHE.computeIfAbsent( - name.toUpperCase(Locale.ROOT), k -> createConnector(name)); - } - - private static Connector createConnector(String name) { - Objects.requireNonNull(name); - Environment environment = Environment.byName(name); - - return new Connector(name, environment); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorSteps.java deleted file mode 100644 index cde597521..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ConnectorSteps.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.java.en.Given; -import java.sql.SQLException; - -public class ConnectorSteps { - - @Given("'{connector}' has an empty database") - public void cleanDatabase(Connector connector) throws SQLException { - connector.getDatabaseCleaner().run(); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java deleted file mode 100644 index 6a7de2ceb..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Constants.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -public final class Constants { - public static final String DATA_MANAGEMENT_URL = "DATA_MANAGEMENT_URL"; - public static final String DATA_MANAGEMENT_API_AUTH_KEY = "DATA_MANAGEMENT_API_AUTH_KEY"; - public static final String IDS_URL = "IDS_URL"; - public static final String DATA_PLANE_URL = "DATA_PLANE_URL"; - public static final String BACKEND_SERVICE_BACKEND_API_URL = "BACKEND_SERVICE_BACKEND_API_URL"; - public static final String DATABASE_URL = "DATABASE_URL"; - public static final String DATABASE_USER = "DATABASE_USER"; - public static final String DATABASE_PASSWORD = "DATABASE_PASSWORD"; - public static final String EDC_AWS_ENDPOINT_OVERRIDE = "EDC_AWS_ENDPOINT_OVERRIDE"; - public static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"; - public static final String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"; -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ContractDefinitionStepDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ContractDefinitionStepDefs.java deleted file mode 100644 index 634a845ff..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ContractDefinitionStepDefs.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import org.eclipse.tractusx.edc.tests.data.ContractDefinition; - -public class ContractDefinitionStepDefs { - - @Given("'{connector}' has the following contract definitions") - public void hasPolicies(Connector connector, DataTable table) throws Exception { - final DataManagementAPI api = connector.getDataManagementAPI(); - final List contractDefinitions = parseDataTable(table); - - for (ContractDefinition contractDefinition : contractDefinitions) - api.createContractDefinition(contractDefinition); - } - - private List parseDataTable(DataTable table) { - final List contractDefinitions = new ArrayList<>(); - - for (Map map : table.asMaps()) { - String id = map.get("id"); - String accessPolicyId = map.get("access policy"); - String contractPolicyId = map.get("contract policy"); - String assetId = map.get("asset"); - List assetIds = assetId == null ? new ArrayList<>() : List.of(assetId); - - String mapValidity = map.get("validity"); - Long validity = mapValidity == null ? null : Long.parseLong(mapValidity); - - contractDefinitions.add( - new ContractDefinition(id, contractPolicyId, accessPolicyId, assetIds, validity)); - } - - return contractDefinitions; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java deleted file mode 100644 index e786c789a..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/ControlPlaneAdapterSteps.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2023 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import com.google.gson.Gson; -import io.cucumber.datatable.DataTable; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.HttpClientBuilder; -import org.eclipse.edc.spi.system.health.HealthStatus; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.junit.jupiter.api.Assertions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Map; - -public class ControlPlaneAdapterSteps { - - private static final Logger log = LoggerFactory.getLogger(ControlPlaneAdapterSteps.class); - private EndpointDataReference endpointDataReference; - - /* - * TODO: see of EndToEndTransfer.feature - * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline - * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 - */ - - // @When("'{connector}' gets a request endpoint from '{connector}'") - public void getEndPointFromGetRequest(Connector consumer, Connector receiver, DataTable table) - throws IOException { - - DataManagementAPI dataManagementAPI = consumer.getDataManagementAPI(); - String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - - for (Map map : table.asMaps()) { - String assetId = map.get("asset id"); - - endpointDataReference = dataManagementAPI.getEdcEndpoint(assetId, receiverIdsUrl); - - log.debug("endpointDataReference in controlplane" + endpointDataReference.toString()); - } - } - - /* - * TODO: see EndToEndTransfer.feature - * the current Bussinnes test is not running, because of a possible rare condition in the CI pipeline - * regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 - */ - - // @Then("'{connector}' asks for the asset from the endpoint") - public void receiveEndpoint(Connector consumer) throws IOException { - - var requestUrl = endpointDataReference.getEndpoint(); - var key = endpointDataReference.getAuthKey(); - var value = endpointDataReference.getAuthCode(); - var httpClient = HttpClientBuilder.create().build(); - var get = new HttpGet(requestUrl); - get.addHeader(key, value); - CloseableHttpResponse response = httpClient.execute(get); - var bytes = response.getEntity().getContent().readAllBytes(); - var result = new String(bytes); - var resultTransformed = new Gson().fromJson(result, HealthStatus.class); - - Assertions.assertTrue(resultTransformed.isHealthy()); - Assertions.assertFalse(resultTransformed.getComponentResults().isEmpty()); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java deleted file mode 100644 index d67dc77ca..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/DataManagementAPI.java +++ /dev/null @@ -1,739 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicHeader; -import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.tractusx.edc.tests.data.Asset; -import org.eclipse.tractusx.edc.tests.data.BusinessPartnerNumberConstraint; -import org.eclipse.tractusx.edc.tests.data.Constraint; -import org.eclipse.tractusx.edc.tests.data.ContractDefinition; -import org.eclipse.tractusx.edc.tests.data.ContractNegotiation; -import org.eclipse.tractusx.edc.tests.data.ContractNegotiationState; -import org.eclipse.tractusx.edc.tests.data.ContractOffer; -import org.eclipse.tractusx.edc.tests.data.DataAddress; -import org.eclipse.tractusx.edc.tests.data.HttpProxySinkDataAddress; -import org.eclipse.tractusx.edc.tests.data.HttpProxySourceDataAddress; -import org.eclipse.tractusx.edc.tests.data.Negotiation; -import org.eclipse.tractusx.edc.tests.data.NullDataAddress; -import org.eclipse.tractusx.edc.tests.data.OrConstraint; -import org.eclipse.tractusx.edc.tests.data.PayMeConstraint; -import org.eclipse.tractusx.edc.tests.data.Permission; -import org.eclipse.tractusx.edc.tests.data.Policy; -import org.eclipse.tractusx.edc.tests.data.S3DataAddress; -import org.eclipse.tractusx.edc.tests.data.Transfer; -import org.eclipse.tractusx.edc.tests.data.TransferProcess; -import org.eclipse.tractusx.edc.tests.data.TransferProcessState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -public class DataManagementAPI { - - private static final Logger log = LoggerFactory.getLogger(DataManagementAPI.class); - private static final String ASSET_PATH = "/assets"; - private static final String POLICY_PATH = "/policydefinitions"; - private static final String CONTRACT_DEFINITIONS_PATH = "/contractdefinitions"; - private static final String CATALOG_PATH = "/catalog"; - private static final String NEGOTIATIONS_PATH = "/contractnegotiations"; - private static final String TRANSFER_PATH = "/transferprocess"; - private static final String ADAPTER_PATH = "/adapter/asset/sync/"; - - private final String dataMgmtUrl; - private final String dataMgmtAuthKey; - private final CloseableHttpClient httpClient; - - public DataManagementAPI(String dataManagementUrl, String dataMgmtAuthKey) { - httpClient = HttpClientBuilder.create().build(); - dataMgmtUrl = dataManagementUrl; - this.dataMgmtAuthKey = dataMgmtAuthKey; - } - - public List requestCatalogFrom(String receivingConnectorUrl) throws IOException { - String encodedUrl = URLEncoder.encode(receivingConnectorUrl, StandardCharsets.UTF_8); - ManagementApiContractOfferCatalog catalog = - get( - CATALOG_PATH, - "providerUrl=" + encodedUrl, - new TypeToken() { - }); - - log.debug("Received " + catalog.contractOffers.size() + " offers"); - - return catalog.contractOffers.stream().map(this::mapOffer).collect(Collectors.toList()); - } - - public Negotiation initiateNegotiation( - String receivingConnectorUrl, String definitionId, String assetId, Policy policy) - throws IOException { - ManagementApiOffer offer = new ManagementApiOffer(); - offer.offerId = definitionId + ":foo"; - offer.assetId = assetId; - offer.policy = mapPolicy(policy); - offer.policy.permissions.forEach(p -> p.target = assetId); - - ManagementApiNegotiationPayload negotiationPayload = - new ManagementApiNegotiationPayload(); - negotiationPayload.connectorAddress = receivingConnectorUrl; - negotiationPayload.offer = offer; - - ManagementApiNegotiationResponse response = - post( - NEGOTIATIONS_PATH, - negotiationPayload, - new TypeToken() { - }); - - if (response == null) { - throw new RuntimeException( - "Initiated negotiation. Connector did not answer with negotiation ID."); - } - - log.info(String.format("Initiated negotiation (id=%s)", response.getId())); - - String negotiationId = response.getId(); - return new Negotiation(negotiationId); - } - - public Transfer initiateTransferProcess( - String receivingConnectorUrl, - String contractAgreementId, - String assetId, - DataAddress dataAddress) - throws IOException { - ManagementApiTransfer transfer = new ManagementApiTransfer(); - - transfer.connectorAddress = receivingConnectorUrl; - transfer.contractId = contractAgreementId; - transfer.assetId = assetId; - transfer.transferType = new ManagementApiTransferType(); - transfer.managedResources = false; - transfer.dataDestination = mapDataAddress(dataAddress); - transfer.protocol = "ids-multipart"; - - return initiateTransferProcess(transfer); - } - - public Transfer initiateTransferProcess( - String receivingConnectorUrl, - String contractAgreementId, - String assetId, - DataAddress dataAddress, - String receiverEndpoint) - throws IOException { - ManagementApiTransfer transfer = new ManagementApiTransfer(); - - transfer.connectorAddress = receivingConnectorUrl; - transfer.contractId = contractAgreementId; - transfer.assetId = assetId; - transfer.transferType = new ManagementApiTransferType(); - transfer.managedResources = false; - transfer.dataDestination = mapDataAddress(dataAddress); - transfer.protocol = "ids-multipart"; - transfer.properties = new ManagementApiProperties(receiverEndpoint); - - return initiateTransferProcess(transfer); - } - - public Asset initiateTransferProcess( - String endpointUrl, String endpointAuthKey, String endpointAuthCode) throws IOException { - Header header = new BasicHeader(endpointAuthKey, endpointAuthCode); - return get(endpointUrl, header, new TypeToken() { - }); - } - - public TransferProcess getTransferProcess(String id) throws IOException { - ManagementApiTransferProcess transferProcess = - get(TRANSFER_PATH + "/" + id, new TypeToken() { - }); - return mapTransferProcess(transferProcess); - } - - public ContractNegotiation getNegotiation(String id) throws IOException { - ManagementApiNegotiation negotiation = - get(NEGOTIATIONS_PATH + "/" + id, new TypeToken() { - }); - return mapNegotiation(negotiation); - } - - public List getNegotiations() throws IOException { - List negotiations = - get(NEGOTIATIONS_PATH + "/", new TypeToken>() { - }); - return negotiations.stream().map(this::mapNegotiation).collect(Collectors.toList()); - } - - public void createAsset(Asset asset) throws IOException { - ManagementApiAssetCreate assetCreate = new ManagementApiAssetCreate(); - - assetCreate.asset = mapAsset(asset); - assetCreate.dataAddress = mapDataAddress(asset.getDataAddress()); - - post(ASSET_PATH, assetCreate); - } - - public void createPolicy(Policy policy) throws IOException { - post(POLICY_PATH, mapPolicyDefinition(policy)); - } - - public void createContractDefinition(ContractDefinition contractDefinition) throws IOException { - post(CONTRACT_DEFINITIONS_PATH, mapContractDefinition(contractDefinition)); - } - - public EndpointDataReference getEdcEndpoint(String assetId, String receivingConnectorUrl) - throws IOException { - String encodedUrl = ADAPTER_PATH + assetId + "?providerUrl=" + receivingConnectorUrl; - - EndpointDataReference endpoint = - get(encodedUrl, new TypeToken() { - }); - - return endpoint; - } - - private Transfer initiateTransferProcess(ManagementApiTransfer transfer) throws IOException { - ManagementApiTransferResponse response = - post(TRANSFER_PATH, transfer, new TypeToken() { - }); - - if (response == null) { - throw new RuntimeException( - "Initiated transfer process. Connector did not answer with transfer process ID."); - } - - log.info(String.format("Initiated transfer process (id=%s)", response.getId())); - - String transferId = response.getId(); - return new Transfer(transferId); - } - - private T get(String path, String params, TypeToken typeToken) throws IOException { - return get(path + "?" + params, typeToken); - } - - private T get(String path, TypeToken typeToken) throws IOException { - - HttpGet get = new HttpGet(dataMgmtUrl + path); - HttpResponse response = sendRequest(get); - byte[] json = response.getEntity().getContent().readAllBytes(); - - log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); - return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); - } - - private T get(String path, Header header, TypeToken typeToken) throws IOException { - - HttpGet get = new HttpGet(path); - get.addHeader(header); - HttpResponse response = sendRequest(get); - byte[] json = response.getEntity().getContent().readAllBytes(); - - log.debug("Received response: {}", new String(json, StandardCharsets.UTF_8)); - return new Gson().fromJson(new String(json, StandardCharsets.UTF_8), typeToken.getType()); - } - - private void post(String path, Object object) throws IOException { - post(path, object, new TypeToken() { - }); - } - - private T post(String path, Object object, TypeToken typeToken) throws IOException { - String url = String.format("%s%s", dataMgmtUrl, path); - HttpPost post = new HttpPost(url); - post.addHeader("Content-Type", "application/json"); - - var json = new Gson().toJson(object); - - log.debug("POST Payload: " + json); - - post.setEntity(new StringEntity(json)); - CloseableHttpResponse response = sendRequest(post); - - T responseJson = null; - if (!typeToken.equals(new TypeToken() { - })) { - byte[] responseBytes = response.getEntity().getContent().readAllBytes(); - responseJson = - new Gson() - .fromJson(new String(responseBytes, StandardCharsets.UTF_8), typeToken.getType()); - } - - response.close(); - - return responseJson; - } - - private CloseableHttpResponse sendRequest(HttpRequestBase request) throws IOException { - request.addHeader("X-Api-Key", dataMgmtAuthKey); - - log.debug(String.format("Send %-6s %s", request.getMethod(), request.getURI())); - - CloseableHttpResponse response = httpClient.execute(request); - if (200 > response.getStatusLine().getStatusCode() - || response.getStatusLine().getStatusCode() >= 300) { - throw new RuntimeException( - String.format("Unexpected response: %s", response.getStatusLine())); - } - - return response; - } - - private ContractNegotiation mapNegotiation(ManagementApiNegotiation negotiation) { - - ContractNegotiationState state; - - switch (negotiation.state) { - case "ERROR": - state = ContractNegotiationState.ERROR; - break; - case "INITIAL": - state = ContractNegotiationState.INITIAL; - break; - case "DECLINED": - state = ContractNegotiationState.DECLINED; - break; - case "CONFIRMED": - state = ContractNegotiationState.CONFIRMED; - break; - default: - state = ContractNegotiationState.UNKNOWN; - } - - return new ContractNegotiation(negotiation.id, state, negotiation.contractAgreementId); - } - - private TransferProcess mapTransferProcess(ManagementApiTransferProcess transferProcess) { - - TransferProcessState state; - - switch (transferProcess.state) { - case "COMPLETED": - state = TransferProcessState.COMPLETED; - break; - case "ERROR": - state = TransferProcessState.ERROR; - break; - default: - state = TransferProcessState.UNKNOWN; - } - - return new TransferProcess(transferProcess.id, state); - } - - private ManagementApiDataAddress mapDataAddress(DataAddress dataAddress) { - Objects.requireNonNull(dataAddress); - ManagementApiDataAddress apiObject = new ManagementApiDataAddress(); - - if (dataAddress instanceof HttpProxySourceDataAddress) { - var address = (HttpProxySourceDataAddress) dataAddress; - var properties = new HashMap(); - properties.put("type", "HttpData"); - properties.put("baseUrl", address.getBaseUrl()); - var oauth2Provision = address.getOauth2Provision(); - if (oauth2Provision != null) { - properties.put("oauth2:tokenUrl", oauth2Provision.getTokenUrl()); - properties.put("oauth2:clientId", oauth2Provision.getClientId()); - properties.put("oauth2:clientSecret", oauth2Provision.getClientSecret()); - properties.put("oauth2:scope", oauth2Provision.getScope()); - } - apiObject.setProperties(properties); - } else if (dataAddress instanceof HttpProxySinkDataAddress) { - apiObject.setProperties(Map.of("type", "HttpProxy")); - } else if (dataAddress instanceof S3DataAddress) { - S3DataAddress a = (S3DataAddress) dataAddress; - apiObject.setProperties( - Map.of( - "type", - "AmazonS3", - "bucketName", - a.getBucketName(), - "region", - a.getRegion(), - "keyName", - a.getKeyName())); - } else if (dataAddress instanceof NullDataAddress) { - // set something that passes validation - apiObject.setProperties(Map.of("type", "HttpData", "baseUrl", "http://localhost")); - } else { - throw new UnsupportedOperationException( - String.format( - "Cannot map data address of type %s to EDC domain", dataAddress.getClass())); - } - - return apiObject; - } - - private ManagementApiAsset mapAsset(Asset asset) { - Map properties = - Map.of( - ManagementApiAsset.ID, asset.getId(), - ManagementApiAsset.DESCRIPTION, asset.getDescription()); - - ManagementApiAsset apiObject = new ManagementApiAsset(); - apiObject.setProperties(properties); - return apiObject; - } - - private Policy mapPolicy(ManagementApiPolicy managementApiPolicy) { - String id = managementApiPolicy.uid; - List permissions = - managementApiPolicy.permissions.stream() - .map(this::mapPermission) - .collect(Collectors.toList()); - - return new Policy(id, permissions); - } - - private ManagementApiPolicy mapPolicy(Policy policy) { - List permissions = - policy.getPermission().stream().map(this::mapPermission).collect(Collectors.toList()); - ManagementApiPolicy managementApiPolicy = new ManagementApiPolicy(); - managementApiPolicy.permissions = permissions; - - return managementApiPolicy; - } - - private ManagementApiPolicyDefinition mapPolicyDefinition(Policy policy) { - ManagementApiPolicyDefinition apiObject = new ManagementApiPolicyDefinition(); - apiObject.id = policy.getId(); - apiObject.policy = mapPolicy(policy); - return apiObject; - } - - private Permission mapPermission(ManagementApiPermission managementApiPermission) { - String target = managementApiPermission.target; - String action = managementApiPermission.action.type; - return new Permission(action, new ArrayList<>(), target); - } - - private ManagementApiPermission mapPermission(Permission permission) { - String target = permission.getTarget(); - String action = permission.getAction(); - - ManagementApiRuleAction apiAction = new ManagementApiRuleAction(); - apiAction.type = action; - - var constraints = - permission.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); - - ManagementApiPermission apiObject = new ManagementApiPermission(); - apiObject.target = target; - apiObject.action = apiAction; - apiObject.constraints = constraints; - return apiObject; - } - - private ManagementConstraint mapConstraint(Constraint constraint) { - if (OrConstraint.class.equals(constraint.getClass())) { - return mapConstraint((OrConstraint) constraint); - } else if (BusinessPartnerNumberConstraint.class.equals(constraint.getClass())) { - return mapConstraint((BusinessPartnerNumberConstraint) constraint); - } else if (PayMeConstraint.class.equals(constraint.getClass())) { - return mapConstraint((PayMeConstraint) constraint); - } else { - throw new UnsupportedOperationException( - "Unsupported constraint type: " + constraint.getClass().getName()); - } - } - - private ManagementAtomicConstraint mapConstraint(PayMeConstraint constraint) { - ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); - leftExpression.value = "PayMe"; - - ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); - rightExpression.value = String.valueOf(constraint.getAmount()); - - ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); - dataManagementApiConstraint.leftExpression = leftExpression; - dataManagementApiConstraint.rightExpression = rightExpression; - dataManagementApiConstraint.operator = "EQ"; - - return dataManagementApiConstraint; - } - - private ManagementAtomicConstraint mapConstraint(BusinessPartnerNumberConstraint constraint) { - ManagementApiLiteralExpression leftExpression = new ManagementApiLiteralExpression(); - leftExpression.value = "BusinessPartnerNumber"; - - ManagementApiLiteralExpression rightExpression = new ManagementApiLiteralExpression(); - rightExpression.value = constraint.getBusinessPartnerNumber(); - - ManagementAtomicConstraint dataManagementApiConstraint = new ManagementAtomicConstraint(); - dataManagementApiConstraint.leftExpression = leftExpression; - dataManagementApiConstraint.rightExpression = rightExpression; - dataManagementApiConstraint.operator = "EQ"; - - return dataManagementApiConstraint; - } - - private ManagementOrConstraint mapConstraint(OrConstraint constraint) { - var orConstraint = new ManagementOrConstraint(); - orConstraint.constraints = - constraint.getConstraints().stream().map(this::mapConstraint).collect(Collectors.toList()); - return orConstraint; - } - - private ContractOffer mapOffer(ManagementApiContractOffer managementApiContractOffer) { - String id = managementApiContractOffer.id; - String assetId = - managementApiContractOffer.assetId != null - ? managementApiContractOffer.assetId - : (String) managementApiContractOffer.asset.getProperties().get(ManagementApiAsset.ID); - - Policy policy = mapPolicy(managementApiContractOffer.getPolicy()); - - return new ContractOffer(id, policy, assetId); - } - - private ManagementApiContractDefinition mapContractDefinition( - ContractDefinition contractDefinition) { - - ManagementApiContractDefinition apiObject = new ManagementApiContractDefinition(); - apiObject.id = contractDefinition.getId(); - apiObject.accessPolicyId = contractDefinition.getAcccessPolicyId(); - apiObject.contractPolicyId = contractDefinition.getContractPolicyId(); - apiObject.criteria = new ArrayList<>(); - - for (String assetId : contractDefinition.getAssetIds()) { - ManagementApiCriterion criterion = new ManagementApiCriterion(); - criterion.operandLeft = ManagementApiAsset.ID; - criterion.operator = "="; - criterion.operandRight = assetId; - - apiObject.criteria.add(criterion); - } - - return apiObject; - } - - private interface ManagementConstraint { - } - - - private static class ManagementApiNegotiationResponse { - private String id; - - - public String getId() { - return id; - } - } - - - private static class ManagementApiNegotiationPayload { - private final String connectorId = "foo"; - private String connectorAddress; - private ManagementApiOffer offer; - } - - private static class ManagementApiNegotiation { - private String id; - private String state; - private String contractAgreementId; - } - - private static class ManagementApiTransferProcess { - private String id; - private String state; - } - - - private static class ManagementApiOffer { - private String offerId; - private String assetId; - private ManagementApiPolicy policy; - } - - - private static class ManagementApiTransfer { - private final String connectorId = "foo"; - private String connectorAddress; - private String contractId; - private String assetId; - private String protocol; - private ManagementApiDataAddress dataDestination; - private boolean managedResources; - private ManagementApiTransferType transferType; - private ManagementApiProperties properties; - } - - - private static class ManagementApiTransferType { - private final String contentType = "application/octet-stream"; - private final boolean isFinite = true; - } - - - private static class ManagementApiTransferResponse { - private String id; - - - public String getId() { - return id; - } - } - - private static class ManagementApiAssetCreate { - private ManagementApiAsset asset; - private ManagementApiDataAddress dataAddress; - } - - private static class ManagementApiAsset { - public static final String ID = "asset:prop:id"; - public static final String DESCRIPTION = "asset:prop:description"; - - private Map properties; - - - public Map getProperties() { - return properties; - } - - public void setProperties(Map properties) { - this.properties = properties; - } - } - - - private static class ManagementApiDataAddress { - public static final String TYPE = "type"; - private Map properties; - - - public Map getProperties() { - return properties; - } - - public void setProperties(Map properties) { - this.properties = properties; - } - } - - - private static class ManagementApiProperties { - @SerializedName(value = "receiver.http.endpoint") - private final String receiverHttpEndpoint; - - private ManagementApiProperties(String receiverHttpEndpoint) { - this.receiverHttpEndpoint = receiverHttpEndpoint; - } - } - - - private static class ManagementApiPolicyDefinition { - private String id; - private ManagementApiPolicy policy; - } - - - private static class ManagementApiPolicy { - private String uid; - private List permissions = new ArrayList<>(); - } - - - private static class ManagementApiPermission { - private final String edctype = "dataspaceconnector:permission"; - private ManagementApiRuleAction action; - private String target; - private List constraints = new ArrayList<>(); - } - - - private static class ManagementAtomicConstraint implements ManagementConstraint { - private final String edctype = "AtomicConstraint"; - private ManagementApiLiteralExpression leftExpression; - private ManagementApiLiteralExpression rightExpression; - private String operator; - } - - - private static class ManagementOrConstraint implements ManagementConstraint { - private final String edctype = "dataspaceconnector:orconstraint"; - private List constraints; - } - - - private static class ManagementApiLiteralExpression { - private final String edctype = "dataspaceconnector:literalexpression"; - private String value; - } - - - private static class ManagementApiRuleAction { - private String type; - } - - - private static class ManagementApiContractDefinition { - private String id; - private String accessPolicyId; - private String contractPolicyId; - private List criteria = new ArrayList<>(); - } - - - private static class ManagementApiCriterion { - private Object operandLeft; - private String operator; - private Object operandRight; - } - - - private static class ManagementApiContractOffer { - private String id; - private ManagementApiPolicy policy; - private ManagementApiAsset asset; - private String assetId; - - - public ManagementApiPolicy getPolicy() { - return policy; - } - } - - - private static class ManagementApiContractOfferCatalog { - private final List contractOffers = new ArrayList<>(); - private String id; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java deleted file mode 100644 index 49a2353d1..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/Environment.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import java.util.Locale; -import java.util.Objects; - -import static org.eclipse.tractusx.edc.tests.Constants.AWS_ACCESS_KEY_ID; -import static org.eclipse.tractusx.edc.tests.Constants.AWS_SECRET_ACCESS_KEY; -import static org.eclipse.tractusx.edc.tests.Constants.BACKEND_SERVICE_BACKEND_API_URL; -import static org.eclipse.tractusx.edc.tests.Constants.DATABASE_PASSWORD; -import static org.eclipse.tractusx.edc.tests.Constants.DATABASE_URL; -import static org.eclipse.tractusx.edc.tests.Constants.DATABASE_USER; -import static org.eclipse.tractusx.edc.tests.Constants.DATA_MANAGEMENT_API_AUTH_KEY; -import static org.eclipse.tractusx.edc.tests.Constants.DATA_MANAGEMENT_URL; -import static org.eclipse.tractusx.edc.tests.Constants.DATA_PLANE_URL; -import static org.eclipse.tractusx.edc.tests.Constants.EDC_AWS_ENDPOINT_OVERRIDE; -import static org.eclipse.tractusx.edc.tests.Constants.IDS_URL; - -public class Environment { - - private String awsEndpointOverride; - private String awsAccessKey; - private String awsSecretAccessKey; - private String dataManagementAuthKey; - private String dataManagementUrl; - private String idsUrl; - private String dataPlaneUrl; - private String backendServiceBackendApiUrl; - private String databaseUrl; - private String databaseUser; - private String databasePassword; - - private Environment() { - - } - - - public static Environment byName(String name) { - var upperName = name.toUpperCase(Locale.ROOT); - - return Environment.Builder.newInstance() - .dataManagementUrl(System.getenv(String.join("_", upperName, DATA_MANAGEMENT_URL))) - .dataManagementAuthKey(System.getenv(String.join("_", upperName, DATA_MANAGEMENT_API_AUTH_KEY))) - .idsUrl(System.getenv(String.join("_", upperName, IDS_URL))) - .dataPlaneUrl(System.getenv(String.join("_", upperName, DATA_PLANE_URL))) - .backendServiceBackendApiUrl( - System.getenv(String.join("_", upperName, BACKEND_SERVICE_BACKEND_API_URL))) - .databaseUrl(System.getenv(String.join("_", upperName, DATABASE_URL))) - .databaseUser(System.getenv(String.join("_", upperName, DATABASE_USER))) - .databasePassword(System.getenv(String.join("_", upperName, DATABASE_PASSWORD))) - .awsEndpointOverride(System.getenv(EDC_AWS_ENDPOINT_OVERRIDE)) - .awsAccessKey(System.getenv(String.join("_", upperName, AWS_ACCESS_KEY_ID))) - .awsSecretAccessKey(System.getenv(String.join("_", upperName, AWS_SECRET_ACCESS_KEY))) - .build(); - } - - public String getIdsUrl() { - return idsUrl; - } - - public String getAwsEndpointOverride() { - return awsEndpointOverride; - } - - public String getAwsSecretAccessKey() { - return awsSecretAccessKey; - } - - public String getAwsAccessKey() { - return awsAccessKey; - } - - public String getBackendServiceBackendApiUrl() { - return backendServiceBackendApiUrl; - } - - public String getDatabasePassword() { - return databasePassword; - } - - public String getDatabaseUrl() { - return databaseUrl; - } - - public String getDatabaseUser() { - return databaseUser; - } - - public String getDataManagementAuthKey() { - return dataManagementAuthKey; - } - - public String getDataManagementUrl() { - return dataManagementUrl; - } - - private static class Builder { - - - private final Environment environment; - - private Builder() { - environment = new Environment(); - } - - public static Builder newInstance() { - return new Builder(); - } - - public Builder awsEndpointOverride(String val) { - environment.awsEndpointOverride = val; - return this; - } - - public Builder awsAccessKey(String val) { - environment.awsAccessKey = val; - return this; - } - - public Builder awsSecretAccessKey(String val) { - environment.awsSecretAccessKey = val; - return this; - } - - public Builder dataManagementAuthKey(String val) { - environment.dataManagementAuthKey = val; - return this; - } - - public Builder dataManagementUrl(String val) { - environment.dataManagementUrl = val; - return this; - } - - public Builder idsUrl(String val) { - environment.idsUrl = val; - return this; - } - - public Builder dataPlaneUrl(String val) { - environment.dataPlaneUrl = val; - return this; - } - - public Builder backendServiceBackendApiUrl(String val) { - environment.backendServiceBackendApiUrl = val; - return this; - } - - public Builder databaseUrl(String val) { - environment.databaseUrl = val; - return this; - } - - public Builder databaseUser(String val) { - environment.databaseUser = val; - return this; - } - - public Builder databasePassword(String val) { - environment.databasePassword = val; - return this; - } - - public Environment build() { - Objects.requireNonNull(environment.awsAccessKey); - Objects.requireNonNull(environment.awsEndpointOverride); - Objects.requireNonNull(environment.awsSecretAccessKey); - Objects.requireNonNull(environment.backendServiceBackendApiUrl); - Objects.requireNonNull(environment.databaseUrl); - Objects.requireNonNull(environment.databasePassword); - Objects.requireNonNull(environment.databaseUser); - Objects.requireNonNull(environment.dataManagementUrl); - Objects.requireNonNull(environment.dataPlaneUrl); - Objects.requireNonNull(environment.dataManagementAuthKey); - Objects.requireNonNull(environment.idsUrl); - return environment; - } - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java deleted file mode 100644 index eee6e6497..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/HttpProxyTransferSteps.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import org.eclipse.tractusx.edc.tests.data.Asset; -import org.eclipse.tractusx.edc.tests.data.DataAddress; -import org.eclipse.tractusx.edc.tests.data.HttpProxySinkDataAddress; -import org.eclipse.tractusx.edc.tests.data.HttpProxySourceDataAddress; -import org.junit.jupiter.api.Assertions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class HttpProxyTransferSteps { - - private static final Logger log = LoggerFactory.getLogger(HttpProxyTransferSteps.class); - - private static final String ID = "id"; - private static final String DESCRIPTION = "description"; - private static final String BASE_URL = "baseUrl"; - private static final String ASSET_ID = "asset id"; - private static final String RECEIVER_HTTP_ENDPOINT = "receiverHttpEndpoint"; - - @Given("'{connector}' has a http proxy assets") - public void hasAssets(Connector connector, DataTable table) throws Exception { - final DataManagementAPI api = connector.getDataManagementAPI(); - - for (var map : table.asMaps()) { - final String id = map.get(ID); - final String description = map.get(DESCRIPTION); - final String baseUrl = map.get(BASE_URL); - - var oauth2Provision = - Arrays.stream(Oauth2DataAddressFields.values()) - .map(it -> it.text) - .anyMatch(map::containsKey) - ? new HttpProxySourceDataAddress.Oauth2Provision( - map.get(Oauth2DataAddressFields.TOKEN_URL.text), - map.get(Oauth2DataAddressFields.CLIENT_ID.text), - map.get(Oauth2DataAddressFields.CLIENT_SECRET.text), - map.get(Oauth2DataAddressFields.SCOPE.text)) - : null; - - final DataAddress address = new HttpProxySourceDataAddress(baseUrl, oauth2Provision); - final Asset asset = new Asset(id, description, address); - - api.createAsset(asset); - } - } - - @When("'{connector}' initiates HttpProxy transfer from '{connector}'") - public void sokratesInitiateHttpProxyTransferProcessFromPlato( - Connector consumer, Connector provider, DataTable dataTable) throws IOException { - var api = consumer.getDataManagementAPI(); - var receiverUrl = provider.getEnvironment().getIdsUrl() + "/data"; - - var negotiation = api.getNegotiations(); - var agreementId = negotiation.get(0).getAgreementId(); - var dataAddress = new HttpProxySinkDataAddress(); - - for (var map : dataTable.asMaps()) { - final String assetId = map.get(ASSET_ID); - final String receiverHttpEndpoint = map.get(RECEIVER_HTTP_ENDPOINT); - var transfer = api.initiateTransferProcess(receiverUrl, agreementId, assetId, dataAddress, receiverHttpEndpoint); - - transfer.waitUntilComplete(api); - } - } - - @Then("the backend application of '{connector}' has received data") - public void theBackendApplicationOfSocratesHasReceivedData(Connector consumer) { - var api = consumer.getBackendServiceBackendAPI(); - when(api.list(eq("/"))).thenReturn(List.of("item1", "item2")); - await() - .atMost(Duration.ofSeconds(20)) - .pollInterval(Duration.ofSeconds(1)) - .untilAsserted(() -> { - final List transferredData = api.list("/"); - Assertions.assertNotEquals(0, transferredData.size()); - }); - } - - private enum Oauth2DataAddressFields { - TOKEN_URL("oauth2 token url"), - CLIENT_ID("oauth2 client id"), - CLIENT_SECRET("oauth2 client secret"), - SCOPE("oauth2 scope"); - - private final String text; - - Oauth2DataAddressFields(String text) { - this.text = text; - } - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java deleted file mode 100644 index 7a713ff1b..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/NegotiationSteps.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import org.eclipse.tractusx.edc.tests.data.ContractNegotiation; -import org.eclipse.tractusx.edc.tests.data.ContractNegotiationState; -import org.eclipse.tractusx.edc.tests.data.Negotiation; -import org.eclipse.tractusx.edc.tests.data.Permission; -import org.eclipse.tractusx.edc.tests.data.Policy; -import org.junit.jupiter.api.Assertions; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - - -public class NegotiationSteps { - - - private static final String DEFINITION_ID = "definition id"; - private static final String ASSET_ID = "asset id"; - - private ContractNegotiation lastInitiatedNegotiation; - - @When("'{connector}' sends '{connector}' an offer without constraints") - public void sendAnOfferWithoutConstraints(Connector sender, Connector receiver, DataTable table) - throws IOException { - - DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - - for (Map map : table.asMaps()) { - String definitionId = map.get(DEFINITION_ID); - String assetId = map.get(ASSET_ID); - - Permission permission = new Permission("USE", new ArrayList<>(), null); - Policy policy = new Policy("foo", List.of(permission)); - - Negotiation negotiation = - dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); - - // wait for negotiation to complete - negotiation.waitUntilComplete(dataManagementAPI); - - lastInitiatedNegotiation = dataManagementAPI.getNegotiation(negotiation.getId()); - } - } - - @When("'{connector}' successfully negotiation a contract agreement with '{connector}'") - public void sokratesSuccessfullyNegotiationAContractAgreementPlatoFor( - Connector consumer, Connector provider, DataTable table) throws IOException { - DataManagementAPI api = consumer.getDataManagementAPI(); - - Map map = table.asMap(); - String definitionId = map.get(DEFINITION_ID); - String assetId = map.get(ASSET_ID); - - // as default always the "allow all" policy is used. So we can assume this here, too. - Permission permission = new Permission("USE", new ArrayList<>(), null); - Policy policy = new Policy("policy-id", List.of(permission)); - - String receiverUrl = provider.getEnvironment().getIdsUrl(); - Negotiation negotiation = - api.initiateNegotiation(receiverUrl, assetId, definitionId, policy); - - negotiation.waitUntilComplete(api); - } - - @Then("the negotiation is declined") - public void assertLastNegotiationDeclined() { - Assertions.assertEquals(ContractNegotiationState.DECLINED, lastInitiatedNegotiation.getState()); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java deleted file mode 100644 index d8bac4466..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/PolicyStepDefs.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.en.Given; -import org.eclipse.tractusx.edc.tests.data.BusinessPartnerNumberConstraint; -import org.eclipse.tractusx.edc.tests.data.Constraint; -import org.eclipse.tractusx.edc.tests.data.OrConstraint; -import org.eclipse.tractusx.edc.tests.data.PayMeConstraint; -import org.eclipse.tractusx.edc.tests.data.Permission; -import org.eclipse.tractusx.edc.tests.data.Policy; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static java.util.Arrays.stream; -import static java.util.stream.Collectors.toList; - -public class PolicyStepDefs { - - @Given("'{connector}' has the following policies") - public void hasPolicies(Connector connector, DataTable table) throws Exception { - var api = connector.getDataManagementAPI(); - var policies = table.asMaps().stream().map(this::parseRow).collect(toList()); - - for (var policy : policies) { - api.createPolicy(policy); - } - } - - private Policy parseRow(Map row) { - var id = row.get("id"); - var action = row.get("action"); - var constraints = new ArrayList(); - - var businessPartnerNumber = row.get("businessPartnerNumber"); - if (businessPartnerNumber != null && !businessPartnerNumber.isBlank()) { - var bpnConstraints = - stream(businessPartnerNumber.split(",")) - .map(BusinessPartnerNumberConstraint::new) - .collect(toList()); - constraints.add(new OrConstraint(bpnConstraints)); - } - - var payMe = row.get("payMe"); - if (payMe != null && !payMe.isBlank()) { - constraints.add(new PayMeConstraint(Double.parseDouble(payMe))); - } - - var permission = new Permission(action, constraints, null); - return new Policy(id, List.of(permission)); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java deleted file mode 100644 index 05f5f1242..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/S3FileTransferStepsDefs.java +++ /dev/null @@ -1,178 +0,0 @@ -/* Copyright (c) 2022 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests; - -import io.cucumber.datatable.DataTable; -import io.cucumber.java.AfterAll; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import org.eclipse.tractusx.edc.tests.data.Asset; -import org.eclipse.tractusx.edc.tests.data.DataAddress; -import org.eclipse.tractusx.edc.tests.data.Negotiation; -import org.eclipse.tractusx.edc.tests.data.Permission; -import org.eclipse.tractusx.edc.tests.data.Policy; -import org.eclipse.tractusx.edc.tests.data.S3DataAddress; -import org.eclipse.tractusx.edc.tests.data.Transfer; -import org.eclipse.tractusx.edc.tests.util.S3Client; -import org.eclipse.tractusx.edc.tests.util.Timeouts; -import org.junit.jupiter.api.Assertions; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.fail; - -public class S3FileTransferStepsDefs { - - private static final String COMPLETION_MARKER = ".complete"; - private File fileToTransfer; - private String assetId; - private String agreementId; - - @AfterAll - public static void bucketsCleanup() { - S3Client s3 = new S3Client(Environment.byName("Sokrates")); - s3.deleteAllBuckets(); - } - - @Given("'{connector}' has an empty storage bucket called {string}") - public void hasEmptyStorageBucket(Connector connector, String bucketName) { - S3Client s3 = connector.getS3Client(); - - s3.createBucket(bucketName); - - Assertions.assertTrue(s3.listBuckets().contains(bucketName)); - Assertions.assertEquals(0, s3.listBucketContent(bucketName).size()); - } - - @Given("'{connector}' has a storage bucket called {string} with the file called {string}") - public void hasAStorageBucketWithTheFile(Connector connector, String bucketName, String fileName) - throws IOException { - - S3Client s3 = connector.getS3Client(); - s3.createBucket(bucketName); - fileToTransfer = s3.uploadFile(bucketName, fileName); - - Set bucketContent = s3.listBucketContent(bucketName); - - Assertions.assertEquals(1, bucketContent.size()); - Assertions.assertTrue(bucketContent.contains(fileName)); - } - - @Given("'{connector}' has the following S3 assets") - public void hasAssets(Connector connector, DataTable table) { - DataManagementAPI api = connector.getDataManagementAPI(); - - parseDataTable(table) - .forEach( - asset -> { - try { - api.createAsset(asset); - } catch (IOException e) { - fail(e.getMessage()); - } - }); - } - - @Then("'{connector}' negotiates the contract successfully with '{connector}'") - public void negotiateContract(Connector sender, Connector receiver, DataTable dataTable) - throws IOException { - - String definitionId = dataTable.asMaps().get(0).get("contract offer id"); - assetId = dataTable.asMaps().get(0).get("asset id"); - String policyId = dataTable.asMaps().get(0).get("policy id"); - - Policy policy = - new Policy(policyId, List.of(new Permission("USE", new ArrayList<>(), null))); - - DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - - Negotiation negotiation = - dataManagementAPI.initiateNegotiation(receiverIdsUrl, definitionId, assetId, policy); - negotiation.waitUntilComplete(dataManagementAPI); - - agreementId = dataManagementAPI.getNegotiation(negotiation.getId()).getAgreementId(); - } - - @Then("'{connector}' initiate S3 transfer process from '{connector}'") - public void initiateTransferProcess(Connector sender, Connector receiver, DataTable dataTable) - throws IOException { - DataAddress dataAddress = createDataAddress(dataTable.asMaps().get(0)); - - DataManagementAPI dataManagementAPI = sender.getDataManagementAPI(); - String receiverIdsUrl = receiver.getEnvironment().getIdsUrl() + "/data"; - - Transfer transferProcess = - dataManagementAPI.initiateTransferProcess( - receiverIdsUrl, agreementId, assetId, dataAddress); - transferProcess.waitUntilComplete(dataManagementAPI); - - Assertions.assertNotNull(transferProcess.getId()); - } - - @Then("'{connector}' has a storage bucket called {string} with transferred file called {string}") - public void consumerHasAStorageBucketWithFileTransferred( - Connector connector, String bucketName, String fileName) throws IOException { - S3Client s3 = connector.getS3Client(); - await() - .pollDelay(Duration.ofMillis(500)) - .atMost(Timeouts.FILE_TRANSFER) - .until(() -> isFilePresent(s3, bucketName, fileName + COMPLETION_MARKER)); - - Set bucketContent = s3.listBucketContent(bucketName); - - Assertions.assertEquals(2, bucketContent.size()); - Assertions.assertTrue(bucketContent.contains(fileName)); - Assertions.assertArrayEquals( - Files.readAllBytes(fileToTransfer.toPath()), - Files.readAllBytes(s3.downloadFile(bucketName, fileName).toPath())); - } - - private boolean isFilePresent(S3Client s3, String bucketName, String fileName) { - return s3.listBucketContent(bucketName).contains(fileName); - } - - private List parseDataTable(DataTable table) { - List assetsWithDataAddresses = new ArrayList<>(); - - for (Map map : table.asMaps()) { - String id = map.get("id"); - String description = map.get("description"); - assetsWithDataAddresses.add(new Asset(id, description, createDataAddress(map))); - } - - return assetsWithDataAddresses; - } - - private DataAddress createDataAddress(Map map) { - String bucketName = map.get("data_address_s3_bucket_name"); - String region = map.get("data_address_s3_region"); - String keyName = map.get("data_address_s3_key_name"); - return new S3DataAddress(bucketName, region, keyName); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java deleted file mode 100644 index 47142d0e9..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Asset.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class Asset { - private final String id; - private final String description; - private final DataAddress dataAddress; - - public Asset(String id, String description, DataAddress dataAddress) { - this.id = Objects.requireNonNull(id); - this.description = Objects.requireNonNull(description); - this.dataAddress = Objects.requireNonNull(dataAddress); - } - - public String getId() { - return id; - } - - public String getDescription() { - return description; - } - - public DataAddress getDataAddress() { - return dataAddress; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java deleted file mode 100644 index a103313ee..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/BusinessPartnerNumberConstraint.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class BusinessPartnerNumberConstraint implements Constraint { - - private final String businessPartnerNumber; - - public BusinessPartnerNumberConstraint(String businessPartnerNumber) { - this.businessPartnerNumber = Objects.requireNonNull(businessPartnerNumber); - } - - public String getBusinessPartnerNumber() { - return businessPartnerNumber; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java deleted file mode 100644 index 73bccde7a..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Constraint.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public interface Constraint { -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java deleted file mode 100644 index c90fe1788..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractDefinition.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.tractusx.edc.tests.data; - -import java.util.List; -import java.util.Objects; - - -public class ContractDefinition { - - private final String id; - - private final String contractPolicyId; - private final String acccessPolicyId; - - private final List assetIds; - private final Long validity; - - public ContractDefinition(String id, String contractPolicyId, String acccessPolicyId, List assetIds, Long validity) { - this.id = Objects.requireNonNull(id); - this.contractPolicyId = Objects.requireNonNull(contractPolicyId); - this.acccessPolicyId = Objects.requireNonNull(acccessPolicyId); - this.assetIds = assetIds; - this.validity = validity; - } - - public String getId() { - return id; - } - - public String getContractPolicyId() { - return contractPolicyId; - } - - public String getAcccessPolicyId() { - return acccessPolicyId; - } - - public List getAssetIds() { - return assetIds; - } - - -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java deleted file mode 100644 index 67f9dafb0..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiation.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class ContractNegotiation { - private final String id; - private final ContractNegotiationState state; - private final String agreementId; - - - public ContractNegotiation(String id, ContractNegotiationState state, String agreementId) { - this.id = Objects.requireNonNull(id); - this.state = Objects.requireNonNull(state); - this.agreementId = agreementId; - } - - public String getId() { - return id; - } - - public ContractNegotiationState getState() { - return state; - } - - public String getAgreementId() { - return agreementId; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiationState.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiationState.java deleted file mode 100644 index 9acd4368f..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractNegotiationState.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public enum ContractNegotiationState { - UNKNOWN, - INITIAL, - DECLINED, - CONFIRMED, - ERROR -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java deleted file mode 100644 index 7ac87cb9a..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/ContractOffer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class ContractOffer { - private final String id; - private final Policy policy; - private final String assetId; - - public ContractOffer(String id, Policy policy, String assetId) { - this.id = Objects.requireNonNull(id); - this.policy = policy; - this.assetId = assetId; - } - - public String getId() { - return id; - } - - public Policy getPolicy() { - return policy; - } - - public String getAssetId() { - return assetId; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/DataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/DataAddress.java deleted file mode 100644 index 16815827f..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/DataAddress.java +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright (c) 2022 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public interface DataAddress {} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java deleted file mode 100644 index 2466438be..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySinkDataAddress.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public class HttpProxySinkDataAddress implements DataAddress { -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java deleted file mode 100644 index b9e92a4e5..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/HttpProxySourceDataAddress.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - - -public class HttpProxySourceDataAddress implements DataAddress { - private final String baseUrl; - private final Oauth2Provision oauth2Provision; - - public HttpProxySourceDataAddress(String baseUrl, Oauth2Provision oauth2Provision) { - this.baseUrl = Objects.requireNonNull(baseUrl); - this.oauth2Provision = oauth2Provision; - } - - public String getBaseUrl() { - return baseUrl; - } - - public Oauth2Provision getOauth2Provision() { - return oauth2Provision; - } - - public static class Oauth2Provision { - private final String tokenUrl; - private final String clientId; - private final String clientSecret; - private final String scope; - - public Oauth2Provision(String tokenUrl, String clientId, String clientSecret, String scope) { - this.tokenUrl = Objects.requireNonNull(tokenUrl); - this.clientId = Objects.requireNonNull(clientId); - this.clientSecret = Objects.requireNonNull(clientSecret); - this.scope = scope; - } - - public String getTokenUrl() { - return tokenUrl; - } - - public String getScope() { - return scope; - } - - public String getClientId() { - return clientId; - } - - public String getClientSecret() { - return clientSecret; - } - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java deleted file mode 100644 index eb650139f..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Negotiation.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import org.eclipse.tractusx.edc.tests.DataManagementAPI; -import org.eclipse.tractusx.edc.tests.util.Timeouts; - -import java.io.IOException; -import java.time.Duration; -import java.util.Objects; -import java.util.stream.Stream; - -import static org.awaitility.Awaitility.await; - - -public class Negotiation { - - - private final String id; - - public Negotiation(String id) { - this.id = Objects.requireNonNull(id); - } - - public void waitUntilComplete(DataManagementAPI dataManagementAPI) { - await() - .pollDelay(Duration.ofMillis(5000)) - .atMost(Timeouts.CONTRACT_NEGOTIATION) - .until(() -> isComplete(dataManagementAPI)); - } - - public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { - var negotiation = dataManagementAPI.getNegotiation(id); - return negotiation != null - && Stream.of( - ContractNegotiationState.ERROR, - ContractNegotiationState.CONFIRMED, - ContractNegotiationState.DECLINED) - .anyMatch((l) -> l.equals(negotiation.getState())); - } - - public String getId() { - return id; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java deleted file mode 100644 index 0757fa4cd..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/NullDataAddress.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public class NullDataAddress implements DataAddress { - - private static final NullDataAddress _instance = new NullDataAddress(); - - public static DataAddress INSTANCE = new NullDataAddress(); - - private NullDataAddress() { - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java deleted file mode 100644 index 14d135f18..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/OrConstraint.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.List; -import java.util.Objects; - - -public class OrConstraint implements Constraint { - - private final List constraints; - - public OrConstraint(List constraints) { - this.constraints = Objects.requireNonNull(constraints); - } - - public List getConstraints() { - return constraints; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java deleted file mode 100644 index 1412b8d76..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/PayMeConstraint.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -/** - * The PayMe constraint should be used when no constraint validation/enforcement in the EDC is - * intended. - */ - -public class PayMeConstraint implements Constraint { - private final double amount; - - public PayMeConstraint(double amount) { - this.amount = amount; - } - - public double getAmount() { - return amount; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java deleted file mode 100644 index e90cbfaf0..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Permission.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.tractusx.edc.tests.data; - -import java.util.List; -import java.util.Objects; - - -public class Permission { - private final String action; - private final List constraints; - private final String target; - - - public Permission(String action, List constraints, String target) { - this.action = Objects.requireNonNull(action); - this.constraints = Objects.requireNonNull(constraints); - this.target = target; - } - - public String getAction() { - return action; - } - - public List getConstraints() { - return constraints; - } - - public String getTarget() { - return target; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java deleted file mode 100644 index c58c79206..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Policy.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.List; -import java.util.Objects; - - -public class Policy { - private final String id; - private final List Permission; - - public Policy(String id, List permission) { - this.id = id; - Permission = Objects.requireNonNull(permission); - } - - public String getId() { - return id; - } - - public List getPermission() { - return Permission; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java deleted file mode 100644 index d46b51ea0..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/S3DataAddress.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class S3DataAddress implements DataAddress { - - private final String bucketName; - private final String region; - private final String keyName; - - public S3DataAddress(String bucketName, String region, String keyName) { - this.bucketName = Objects.requireNonNull(bucketName); - this.region = Objects.requireNonNull(region); - this.keyName = Objects.requireNonNull(keyName); - } - - public String getBucketName() { - return bucketName; - } - - public String getRegion() { - return region; - } - - public String getKeyName() { - return keyName; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java deleted file mode 100644 index de409252d..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/Transfer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2023 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import org.eclipse.tractusx.edc.tests.DataManagementAPI; -import org.eclipse.tractusx.edc.tests.util.Timeouts; - -import java.io.IOException; -import java.time.Duration; - -import static org.awaitility.Awaitility.await; - - -public class Transfer { - - private final String id; - - public Transfer(String id) { - this.id = id; - } - - public void waitUntilComplete(DataManagementAPI dataManagementAPI) { - await() - .pollDelay(Duration.ofMillis(2000)) - .atMost(Timeouts.FILE_TRANSFER) - .until(() -> isComplete(dataManagementAPI)); - } - - public boolean isComplete(DataManagementAPI dataManagementAPI) throws IOException { - var transferProcess = dataManagementAPI.getTransferProcess(id); - if (transferProcess == null) { - return false; - } - - var state = transferProcess.getState(); - - return state == TransferProcessState.COMPLETED || state == TransferProcessState.ERROR; - } - - public String getId() { - return id; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java deleted file mode 100644 index 1c00e86c3..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcess.java +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (c) 2022 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -import java.util.Objects; - -public class TransferProcess { - private final String id; - private final TransferProcessState state; - - public TransferProcess(String id, TransferProcessState state) { - this.id = Objects.requireNonNull(id); - this.state = Objects.requireNonNull(state); - } - - public String getId() { - return id; - } - - public TransferProcessState getState() { - return state; - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcessState.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcessState.java deleted file mode 100644 index 4f5612334..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/data/TransferProcessState.java +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright (c) 2022 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.data; - -public enum TransferProcessState { - COMPLETED, - ERROR, - UNKNOWN -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/ParameterTypes.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/ParameterTypes.java deleted file mode 100644 index c353ec2fb..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/ParameterTypes.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.features; - -import io.cucumber.java.ParameterType; -import org.eclipse.tractusx.edc.tests.Connector; -import org.eclipse.tractusx.edc.tests.ConnectorFactory; - -public class ParameterTypes { - - @ParameterType("Plato|Sokrates") - public Connector connector(String name) { - return ConnectorFactory.byName(name); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/RunCucumberTest.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/RunCucumberTest.java deleted file mode 100644 index d9ab3254c..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/features/RunCucumberTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.features; - -import org.junit.platform.suite.api.SelectClasspathResource; -import org.junit.platform.suite.api.Suite; - -@Suite -@SelectClasspathResource("org/eclipse/tractusx/edc/tests/features") -public class RunCucumberTest {} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java deleted file mode 100644 index e03f38e98..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/DatabaseCleaner.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.util; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Statement; - - -public class DatabaseCleaner { - - private static final String SQL = - "DELETE FROM edc_contract_negotiation;\n" - + "DELETE FROM edc_contract_agreement;\n" - + "DELETE FROM edc_transfer_process;\n" - + "DELETE FROM edc_contract_definitions;\n" - + "DELETE FROM edc_policydefinitions;\n" - + "DELETE FROM edc_asset;\n" - + "DELETE FROM edc_lease;"; - - private final String url; - private final String user; - private final String password; - - public DatabaseCleaner(String url, String user, String password) { - this.url = url; - this.user = user; - this.password = password; - } - - public void run() throws SQLException { - try (Connection con = DriverManager.getConnection(url, user, password)) { - Statement st = con.createStatement(); - st.executeUpdate(SQL); - } - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java deleted file mode 100644 index 63ab60324..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/S3Client.java +++ /dev/null @@ -1,126 +0,0 @@ -/* Copyright (c) 2022 ZF Friedrichshafen AG - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.util; - -import org.eclipse.tractusx.edc.tests.Environment; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.core.ResponseBytes; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.model.Bucket; -import software.amazon.awssdk.services.s3.model.BucketAlreadyOwnedByYouException; -import software.amazon.awssdk.services.s3.model.CreateBucketRequest; -import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.ListObjectsRequest; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.model.S3Object; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - - -public class S3Client { - private static final Logger log = LoggerFactory.getLogger(S3Client.class); - private final software.amazon.awssdk.services.s3.S3Client s3; - - public S3Client(Environment environment) { - - s3 = - software.amazon.awssdk.services.s3.S3Client.builder() - .region(Region.US_EAST_1) - .forcePathStyle(true) - .endpointOverride(URI.create(environment.getAwsEndpointOverride())) - .credentialsProvider( - StaticCredentialsProvider.create( - AwsBasicCredentials.create( - environment.getAwsAccessKey(), environment.getAwsSecretAccessKey()))) - .build(); - } - - public void createBucket(String bucketName) { - try { - s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); - } catch (BucketAlreadyOwnedByYouException e) { - log.info("'{}' bucket already owned - skipped bucket creation", bucketName); - } - } - - public File uploadFile(String bucketName, String fileName) throws IOException { - File tempFile = File.createTempFile(fileName, null); - Files.write( - tempFile.toPath(), "Will fail if the file has no content".getBytes(StandardCharsets.UTF_8)); - - s3.putObject( - PutObjectRequest.builder().bucket(bucketName).key(fileName).build(), - RequestBody.fromFile(tempFile)); - - return tempFile; - } - - public List listBuckets() { - return s3.listBuckets().buckets().stream().map(Bucket::name).collect(Collectors.toList()); - } - - public Set listBucketContent(String bucketName) { - return s3 - .listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) - .contents() - .stream() - .map(S3Object::key) - .collect(Collectors.toSet()); - } - - public File downloadFile(String bucketName, String fileName) throws IOException { - ResponseBytes objectAsBytes = - s3.getObjectAsBytes(GetObjectRequest.builder().bucket(bucketName).key(fileName).build()); - - return Files.write(File.createTempFile(fileName, null).toPath(), objectAsBytes.asByteArray()) - .toFile(); - } - - public void deleteAllBuckets() { - List buckets = s3.listBuckets().buckets(); - buckets.forEach(this::clearBucket); - buckets.forEach( - bucket -> s3.deleteBucket(DeleteBucketRequest.builder().bucket(bucket.name()).build())); - } - - private void clearBucket(Bucket bucket) { - String bucketName = bucket.name(); - s3.listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) - .contents() - .forEach( - s3Object -> - s3.deleteObject( - DeleteObjectRequest.builder().bucket(bucketName).key(s3Object.key()).build())); - } -} diff --git a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/Timeouts.java b/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/Timeouts.java deleted file mode 100644 index 0d67c0e5a..000000000 --- a/edc-tests/cucumber/src/test/java/org/eclipse/tractusx/edc/tests/util/Timeouts.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH - * Copyright (c) 2021,2022 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.tests.util; - -import java.time.Duration; - -public class Timeouts { - private Timeouts() {} - - public static final Duration CONTRACT_NEGOTIATION = Duration.ofSeconds(90); - public static final Duration FILE_TRANSFER = Duration.ofSeconds(90); -} diff --git a/edc-tests/cucumber/src/test/resources/junit-platform.properties b/edc-tests/cucumber/src/test/resources/junit-platform.properties deleted file mode 100644 index 215cb0967..000000000 --- a/edc-tests/cucumber/src/test/resources/junit-platform.properties +++ /dev/null @@ -1,3 +0,0 @@ -cucumber.publish.quiet=true -cucumber.publish.enabled=false -cucumber.plugin=json:target/cucumber-reports/,pretty diff --git a/edc-tests/cucumber/src/test/resources/logback-test.xml b/edc-tests/cucumber/src/test/resources/logback-test.xml deleted file mode 100644 index d8c11cbef..000000000 --- a/edc-tests/cucumber/src/test/resources/logback-test.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractNegotiation.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractNegotiation.feature deleted file mode 100644 index 623384d93..000000000 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractNegotiation.feature +++ /dev/null @@ -1,58 +0,0 @@ -# -# Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2021,2022 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -Feature: Contract Negotiation - - Background: The Connector State - Given 'Plato' has an empty database - Given 'Sokrates' has an empty database - - Scenario: Counter Offers are rejected - Given 'Plato' has the following assets - | id | description | - | asset-1 | Example Asset | - And 'Plato' has the following policies - | id | action | payMe | - | policy-1 | USE | | - | policy-pay-me | USE | 1000 | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-pay-me | asset-1 | - When 'Sokrates' sends 'Plato' an offer without constraints - | definition id | asset id | - | contract-definition-1 | asset-1 | - Then the negotiation is declined - - - Scenario: An offer is rejected - Given 'Plato' has the following assets - | id | description | - | asset-1 | Example Asset | - And 'Plato' has the following policies - | id | action | payMe | - | policy-1 | USE | | - | policy-pay-me | USE | 1000 | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | validity | - | contract-definition-1 | policy-1 | policy-pay-me | asset-1 | 1 | - When 'Sokrates' sends 'Plato' an offer without constraints - | definition id | asset id | - | contract-definition-1 | asset-1 | - Then the negotiation is declined \ No newline at end of file diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractOffers.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractOffers.feature deleted file mode 100644 index 3a07016aa..000000000 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/ContractOffers.feature +++ /dev/null @@ -1,97 +0,0 @@ -# -# Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2021,2022 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -Feature: Contract Offers - - Background: The Connector State - Given 'Plato' has an empty database - Given 'Sokrates' has an empty database - - Scenario: Catalog Request - Given 'Plato' has the following assets - | id | description | - | asset-1 | Example Asset | - | asset-2 | Example Asset | - And 'Plato' has the following policies - | id | action | - | policy-1 | USE | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - | contract-definition-2 | policy-1 | policy-1 | asset-2 | - When 'Sokrates' requests the catalog from 'Plato' - Then the catalog contains the following offers - | source definition | asset | - | contract-definition-1 | asset-1 | - | contract-definition-2 | asset-2 | - - Scenario: EQ Business Partner Constrain for Catalog - Given 'Plato' has the following assets - | id | description | - | asset-1 | Example Asset | - | asset-2 | Example Asset | - | asset-3 | Example Asset | - And 'Plato' has the following policies - | id | action | businessPartnerNumber | - | policy-1 | USE | | - | policy-2 | USE | BPNFOO | - | policy-3 | USE | BPNSOKRATES,BPNF00 | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - | contract-definition-2 | policy-2 | policy-1 | asset-2 | - | contract-definition-3 | policy-3 | policy-1 | asset-3 | - When 'Sokrates' requests the catalog from 'Plato' - Then the catalog contains the following offers - | source definition | asset | - | contract-definition-1 | asset-1 | - | contract-definition-3 | asset-3 | - Then the catalog does not contain the following offers - | source definition | asset | - | contract-definition-2 | asset-2 | - - Scenario: Multiple Contract Offers for same Asset - Given 'Plato' has the following assets - | id | description | - | asset-1 | Example Asset | - And 'Plato' has the following policies - | id | action | businessPartnerNumber | - | policy-1 | USE | | - | policy-2 | USE | BPNSOKRATES | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - | contract-definition-2 | policy-2 | policy-1 | asset-1 | - When 'Sokrates' requests the catalog from 'Plato' - Then the catalog contains the following offers - | source definition | asset | - | contract-definition-2 | asset-1 | - #| contract-definition-1 | asset-1 | # Issue https://github.com/eclipse-edc/Connector/issues/1764 - - Scenario: Catalog with 1000 Contract Offers - Given 'Plato' has '1000' assets - And 'Plato' has the following policies - | id | action | - | policy-1 | USE | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | - | contract-definition-1 | policy-1 | policy-1 | - When 'Sokrates' requests the catalog from 'Plato' -#Then the catalog contains '1000' offers # Issue https://github.com/eclipse-edc/Connector/issues/2064 diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/EndToEndTransfer.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/EndToEndTransfer.feature deleted file mode 100644 index 263d41149..000000000 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/EndToEndTransfer.feature +++ /dev/null @@ -1,43 +0,0 @@ -# -# Copyright (c) 2023 ZF Friedrichshafen AG -# Copyright (c) 2021,2022 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -#TODO: the current Bussinnes test is not running, because of a possible condition in the CI pipeline - # regarding the contract validity: see https://github.com/eclipse-edc/Connector/issues/2514 - -#Feature: API-Wrapper Extension - - #Background: The Connector State - #Given 'Plato' has an empty database - #Given 'Sokrates' has an empty database - - #Scenario: Connector asks for an endpoint connector from another one - #Given 'Plato' has a http proxy assets - # | id | description | baseUrl | - # | asset-1 | http proxy transfer asset | http://localhost:8080/api/check/liveness | - #And 'Plato' has the following policies - # | id | action | - # | policy-1 | USE | - #And 'Plato' has the following contract definitions - # | id | access policy | contract policy | asset | - # | contract-definition-1 | policy-1 | policy-1 | asset-1 | - #When 'Sokrates' gets a request endpoint from 'Plato' - # | asset id | - # | asset-1 | - #Then 'Sokrates' asks for the asset from the endpoint \ No newline at end of file diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature deleted file mode 100644 index b04970ec7..000000000 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/HttpProxyDataTransfer.feature +++ /dev/null @@ -1,43 +0,0 @@ -# -# Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH -# Copyright (c) 2021,2022 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -Feature: HttpProxy Data Transfer - - Background: The Connector State - Given 'Plato' has an empty database - Given 'Sokrates' has an empty database - - Scenario: Connector transfers data via HttpProxy, data on provider side requires oauth2 authentication - Given 'Plato' has a http proxy assets - | id | description | baseUrl | oauth2 token url | oauth2 client id | oauth2 client secret | oauth2 scope | - | asset-1 | http proxy transfer asset | http://localhost:8081/api/check/liveness | http://ids-daps:4567 | data-plane-oauth2 | supersecret | openid | - And 'Plato' has the following policies - | id | action | - | policy-1 | USE | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - When 'Sokrates' negotiates the contract successfully with 'Plato' - | contract offer id | asset id | policy id | - | contract-definition-1 | asset-1 | policy-1 | - And 'Sokrates' initiates HttpProxy transfer from 'Plato' - | asset id | - | asset-1 | - Then the backend application of 'Sokrates' has received data diff --git a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/S3FileTransfer.feature b/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/S3FileTransfer.feature deleted file mode 100644 index fa96f9a8f..000000000 --- a/edc-tests/cucumber/src/test/resources/org/eclipse/tractusx/edc/tests/features/S3FileTransfer.feature +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2022 ZF Friedrichshafen AG -# Copyright (c) 2021,2022 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -Feature: S3 File Transfer - - Background: The Connector State - Given 'Plato' has an empty database - Given 'Sokrates' has an empty database - Given 'Sokrates' has an empty storage bucket called 'destinationbucket' - Given 'Plato' has a storage bucket called 'sourcebucket' with the file called 'testfile' - - Scenario: Request file transfer via S3 - Given 'Plato' has the following S3 assets - | id | description | data_address_type | data_address_s3_bucket_name | data_address_s3_key_name | data_address_s3_region | - | asset-1 | Example Asset | AmazonS3 | sourcebucket | testfile | us-east-1 | - And 'Plato' has the following policies - | id | action | payMe | - | policy-1 | USE | | - And 'Plato' has the following contract definitions - | id | access policy | contract policy | asset | - | contract-definition-1 | policy-1 | policy-1 | asset-1 | - When 'Sokrates' requests the catalog from 'Plato' - Then the catalog contains the following offers - | source definition | asset | - | contract-definition-1 | asset-1 | - Then 'Sokrates' negotiates the contract successfully with 'Plato' - | contract offer id | asset id | policy id | - | contract-definition-1 | asset-1 | policy-1 | - Then 'Sokrates' initiate S3 transfer process from 'Plato' - | data_address_s3_bucket_name | data_address_s3_key_name | data_address_s3_region | - | destinationbucket | testfile | us-east-1 | - Then 'Sokrates' has a storage bucket called 'destinationbucket' with transferred file called 'testfile' diff --git a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml index cf5eb97d0..f1f072f6c 100644 --- a/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml +++ b/edc-tests/deployment/src/main/resources/helm/omejdn/templates/hpa.yaml @@ -1,20 +1,20 @@ # Copyright (c) 2023 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License, Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + # License for the specific language governing permissions and limitations + # under the License. + # + # SPDX-License-Identifier: Apache-2.0 + # {{- if .Values.autoscaling.enabled }} --- diff --git a/edc-tests/e2e-tests/build.gradle.kts b/edc-tests/e2e-tests/build.gradle.kts index d716d89c2..237c134d9 100644 --- a/edc-tests/e2e-tests/build.gradle.kts +++ b/edc-tests/e2e-tests/build.gradle.kts @@ -17,22 +17,24 @@ plugins { } dependencies { - testImplementation("com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11") + testImplementation(project(":edc-extensions:control-plane-adapter-api")) + testImplementation(libs.okhttp.mockwebserver) testImplementation(libs.restAssured) testImplementation(libs.postgres) testImplementation(libs.awaitility) testImplementation(libs.aws.s3) - testImplementation(edc.spi.core) - testImplementation(edc.junit) - testImplementation(edc.spi.policy) - testImplementation(edc.spi.contract) - testImplementation(edc.core.api) - testImplementation(edc.spi.catalog) - testImplementation(edc.api.catalog) - testImplementation(edc.api.contractnegotiation) - testImplementation(edc.api.transferprocess) - testImplementation(edc.spi.dataplane.selector) - + testImplementation(libs.edc.spi.core) + testImplementation(libs.edc.junit) + testImplementation(libs.edc.spi.policy) + testImplementation(libs.edc.spi.contract) + testImplementation(libs.edc.core.api) + testImplementation(libs.edc.spi.catalog) + testImplementation(libs.edc.api.catalog) + testImplementation(libs.edc.api.contractnegotiation) + testImplementation(libs.edc.api.transferprocess) + testImplementation(libs.edc.spi.dataplane.selector) + testImplementation(libs.edc.ext.jsonld) + testImplementation(libs.edc.dsp) } // do not publish diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java new file mode 100644 index 000000000..74a24e9b2 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/AssetHelperFunctions.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; + +public class AssetHelperFunctions { + + /** + * Creates an asset with the given ID and props using the participant's Data Management API + */ + public static JsonObject createAsset(String id, JsonObject assetProperties, JsonObject dataAddress) { + return Json.createObjectBuilder() + .add(CONTEXT, createContextBuilder()) + .add(TYPE, EDC_NAMESPACE + "AssetEntryDto") + .add(EDC_NAMESPACE + "asset", Json.createObjectBuilder() + .add(ID, id) + .add(EDC_NAMESPACE + "properties", assetProperties) + .build()) + .add(EDC_NAMESPACE + "dataAddress", dataAddress) + .build(); + + + } + + public static JsonObjectBuilder createDataAddressBuilder(String type) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", type); + } + + public static JsonObjectBuilder createContextBuilder() { + return Json.createObjectBuilder() + .add(EDC_PREFIX, EDC_NAMESPACE); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java new file mode 100644 index 000000000..3803ea9e3 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/CatalogHelperFunctions.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.connector.contract.spi.ContractId; + +import static org.eclipse.edc.catalog.spi.CatalogRequest.EDC_CATALOG_REQUEST_QUERY_SPEC; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class CatalogHelperFunctions { + + + public static JsonObject createCatalogRequest(JsonObject query, String dspEndpoint) { + var jsonBuilder = Json.createObjectBuilder(); + jsonBuilder.add("@type", "CatalogRequest"); + jsonBuilder.add(EDC_NAMESPACE + "providerUrl", dspEndpoint); + jsonBuilder.add(EDC_NAMESPACE + "protocol", "dataspace-protocol-http"); + + if (query != null) { + jsonBuilder.add(EDC_CATALOG_REQUEST_QUERY_SPEC, query); + } + return jsonBuilder.build(); + } + + public static ContractId getDatasetContractId(JsonObject dataset) { + var id = dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE).get(0).asJsonObject().getString(ID); + return ContractId.parse(id); + } + + public static String getDatasetAssetId(JsonObject dataset) { + return getDatasetContractId(dataset).assetIdPart(); + } + + public static String getDatasetAssetId(JsonValue dataset) { + return getDatasetContractId(dataset.asJsonObject()).assetIdPart(); + } + + public static JsonArray getDatasetPolicies(JsonObject dataset) { + return dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE); + } + + public static JsonObject getDatasetFirstPolicy(JsonObject dataset) { + return dataset.getJsonArray(ODRL_POLICY_ATTRIBUTE).stream().findFirst().get().asJsonObject(); + } + + public static JsonObject getDatasetFirstPolicy(JsonValue dataset) { + return getDatasetFirstPolicy(dataset.asJsonObject()); + } + + public static JsonArray getDatasetPolicies(JsonValue dataset) { + return getDatasetPolicies(dataset.asJsonObject()); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java new file mode 100644 index 000000000..4377949ce --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractDefinitionHelperFunctions.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class ContractDefinitionHelperFunctions { + + public static JsonObject createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId) { + return Json.createObjectBuilder() + .add(ID, definitionId) + .add(TYPE, EDC_NAMESPACE + "ContractDefinition") + .add(EDC_NAMESPACE + "accessPolicyId", accessPolicyId) + .add(EDC_NAMESPACE + "contractPolicyId", contractPolicyId) + .add(EDC_NAMESPACE + "criteria", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add(TYPE, "CriterionDto") + .add(EDC_NAMESPACE + "operandLeft", EDC_NAMESPACE + "id") + .add(EDC_NAMESPACE + "operator", "=") + .add(EDC_NAMESPACE + "operandRight", assetId) + .build()) + .build()) + .build(); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java new file mode 100644 index 000000000..d11e176e7 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/ContractNegotiationHelperFunctions.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.mockito.Mockito.mock; + +public class ContractNegotiationHelperFunctions { + + private final static JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + + public static JsonObject createNegotiationRequest(String connectorAddress, String providerId, String offerId, String assetId, JsonObject policy) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "NegotiationInitiateRequestDto") + .add(EDC_NAMESPACE + "connectorId", providerId) + .add(EDC_NAMESPACE + "providerId", providerId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "offer", Json.createObjectBuilder() + .add(EDC_NAMESPACE + "offerId", offerId) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "policy", jsonLd.compact(policy).getContent()) + ) + .build(); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java new file mode 100644 index 000000000..7b0f85d3a --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/EdrNegotiationHelperFunctions.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.spi.monitor.Monitor; + +import java.util.Set; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.mockito.Mockito.mock; + +public class EdrNegotiationHelperFunctions { + + private final static JsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + + public static JsonObject createEdrNegotiationRequest(String connectorAddress, String providerId, String offerId, String assetId, JsonObject policy, JsonArray callbacks) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "NegotiateEdrRequestDto") + .add(EDC_NAMESPACE + "connectorId", providerId) + .add(EDC_NAMESPACE + "providerId", providerId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "offer", Json.createObjectBuilder() + .add(EDC_NAMESPACE + "offerId", offerId) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "policy", jsonLd.compact(policy).getContent()) + ) + .add(EDC_NAMESPACE + "callbackAddresses", callbacks) + .build(); + } + + public static JsonObject createCallback(String url, boolean transactional, Set events) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "CallbackAddress") + .add(EDC_NAMESPACE + "transactional", transactional) + .add(EDC_NAMESPACE + "uri", url) + .add(EDC_NAMESPACE + "events", events + .stream() + .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add) + .build()) + .build(); + } + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java new file mode 100644 index 000000000..7ff41e964 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + + +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import org.eclipse.edc.connector.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Operator; + +import java.util.stream.Stream; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ACTION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OR_CONSTRAINT_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_RIGHT_OPERAND_ATTRIBUTE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class PolicyHelperFunctions { + + private static final String BUSINESS_PARTNER_EVALUATION_KEY = EDC_NAMESPACE + "BusinessPartnerNumber"; + + /** + * Creates a {@link PolicyDefinition} using the given ID, that contains equality constraints for each of the given BusinessPartnerNumbers: + * each BPN is converted into an {@link AtomicConstraint} {@code BusinessPartnerNumber EQ [BPN]}. + */ + public static JsonObject businessPartnerNumberPolicy(String id, String... bpns) { + return policyDefinitionBuilder(bnpPolicy(bpns)) + .add(ID, id) + .build(); + } + + public static JsonObjectBuilder policyDefinitionBuilder() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "PolicyDefinitionDto"); + } + + public static JsonObjectBuilder policyDefinitionBuilder(JsonObject policy) { + return policyDefinitionBuilder() + .add(EDC_NAMESPACE + "policy", policy); + } + + public static JsonObject noConstraintPolicyDefinition(String id) { + return policyDefinitionBuilder(noConstraintPolicy()) + .add(ID, id) + .build(); + } + + private static JsonObject noConstraintPolicy() { + return Json.createObjectBuilder() + .add(TYPE, "use") + .build(); + } + + private static JsonObject bnpPolicy(String... bnps) { + return Json.createObjectBuilder() + .add(ODRL_PERMISSION_ATTRIBUTE, Json.createArrayBuilder() + .add(permission(bnps))) + .build(); + } + + private static JsonObject permission(String... bpns) { + + var bpnConstraints = Stream.of(bpns) + .map(bpn -> atomicConstraint(BUSINESS_PARTNER_EVALUATION_KEY, Operator.EQ, bpn)) + .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); + + return Json.createObjectBuilder() + .add(ODRL_ACTION_ATTRIBUTE, "USE") + .add(ODRL_CONSTRAINT_ATTRIBUTE, Json.createObjectBuilder() + .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) + .add(ODRL_OR_CONSTRAINT_ATTRIBUTE, bpnConstraints) + .build()) + .build(); + } + + private static JsonObject atomicConstraint(String leftOperand, Operator operator, Object rightOperand) { + return Json.createObjectBuilder() + .add(TYPE, ODRL_CONSTRAINT_TYPE) + .add(ODRL_LEFT_OPERAND_ATTRIBUTE, leftOperand) + .add(ODRL_OPERATOR_ATTRIBUTE, operator.toString()) + .add(ODRL_RIGHT_OPERAND_ATTRIBUTE, rightOperand.toString()) + .build(); + } + + +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java new file mode 100644 index 000000000..df70cbab5 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/QueryHelperFunctions.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_LIMIT; +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_OFFSET; +import static org.eclipse.edc.api.query.QuerySpecDto.EDC_QUERY_SPEC_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; + +public class QueryHelperFunctions { + + public static JsonObject createQuery(int limit, int offset) { + return Json.createObjectBuilder() + .add(TYPE, EDC_QUERY_SPEC_TYPE) + .add(EDC_QUERY_SPEC_LIMIT, limit) + .add(EDC_QUERY_SPEC_OFFSET, offset) + .build(); + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java new file mode 100644 index 000000000..7201c6cb7 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/TransferProcessHelperFunctions.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.helpers; + +import jakarta.json.Json; +import jakarta.json.JsonObject; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.protocol.dsp.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public class TransferProcessHelperFunctions { + + public static JsonObject createTransferRequest(String dataRequestId, String connectorAddress, String contractId, String assetId, boolean managedResources, JsonObject destination) { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "TransferRequestDto") + .add(ID, dataRequestId) + .add(EDC_NAMESPACE + "dataDestination", destination) + .add(EDC_NAMESPACE + "protocol", DATASPACE_PROTOCOL_HTTP) + .add(EDC_NAMESPACE + "assetId", assetId) + .add(EDC_NAMESPACE + "contractId", contractId) + .add(EDC_NAMESPACE + "connectorAddress", connectorAddress) + .add(EDC_NAMESPACE + "managedResources", managedResources) + + .build(); + + } + + + public static JsonObject createProxyRequest() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", "HttpProxy") + .build(); + + } +} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java index e99cf0989..45bfba659 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/DataWiper.java @@ -47,7 +47,7 @@ public void clearContractDefinitions() { public void clearPolicies() { var ps = context.getService(PolicyDefinitionStore.class); // must .collect() here, otherwise we'll get a ConcurrentModificationException - ps.findAll(QuerySpec.max()).collect(Collectors.toList()).forEach(p -> ps.deleteById(p.getId())); + ps.findAll(QuerySpec.max()).collect(Collectors.toList()).forEach(p -> ps.delete(p.getId())); } public void clearAssetIndex() { diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java index 5bf2a2417..404bfbe1d 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/MultiRuntimeTest.java @@ -19,44 +19,47 @@ import java.util.HashMap; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.IDS_PATH; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.DSP_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_CONNECTOR_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DATAPLANE_CONTROL_PORT; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_IDS_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DSP_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_MANAGEMENT_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_PUBLIC_API_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_CONNECTOR_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DATAPLANE_CONTROL_PORT; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API; -import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_IDS_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DSP_API_PORT; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PATH; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_MANAGEMENT_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_PUBLIC_API_PORT; public class MultiRuntimeTest { - + public static final String BPN_SUFFIX = "-BPN"; + public static final String SOKRATES_NAME = "SOKRATES"; + public static final String SOKRATES_BPN = SOKRATES_NAME + BPN_SUFFIX; @RegisterExtension protected static Participant sokrates = new Participant( ":edc-tests:runtime", - "SOKRATES", + SOKRATES_NAME, + SOKRATES_BPN, new HashMap<>() { { put("edc.connector.name", "sokrates"); - put("edc.ids.id", "urn:connector:sokrates"); + put("edc.participant.id", SOKRATES_BPN); put("web.http.port", String.valueOf(SOKRATES_CONNECTOR_PORT)); put("web.http.path", SOKRATES_CONNECTOR_PATH); put("web.http.management.port", String.valueOf(SOKRATES_MANAGEMENT_PORT)); put("web.http.management.path", SOKRATES_MANAGEMENT_PATH); - put("web.http.ids.port", String.valueOf(SOKRATES_IDS_API_PORT)); - put("web.http.ids.path", IDS_PATH); + put("web.http.protocol.port", String.valueOf(SOKRATES_DSP_API_PORT)); + put("web.http.protocol.path", DSP_PATH); + put("edc.dsp.callback.address", SOKRATES_DSP_CALLBACK); put("edc.api.auth.key", "testkey"); - put("ids.webhook.address", SOKRATES_IDS_API); put("web.http.public.path", "/api/public"); put("web.http.public.port", SOKRATES_PUBLIC_API_PORT); @@ -70,25 +73,29 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + SOKRATES_PUBLIC_API_PORT + "/api/public\"}"); put("edc.receiver.http.dynamic.endpoint", "http://localhost:" + SOKRATES_CONNECTOR_PORT + "/api/consumer/datareference"); put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); + put("edc.agent.identity.key", "BusinessPartnerNumber"); } }); + public static final String PLATO_NAME = "PLATO"; + public static final String PLATO_BPN = PLATO_NAME + BPN_SUFFIX; @RegisterExtension protected static Participant plato = new Participant( ":edc-tests:runtime", - "PLATO", + PLATO_NAME, + PLATO_BPN, new HashMap<>() { { put("edc.connector.name", "plato"); - put("edc.ids.id", "urn:connector:plato"); + put("edc.participant.id", PLATO_BPN); put("web.http.default.port", String.valueOf(PLATO_CONNECTOR_PORT)); put("web.http.default.path", PLATO_CONNECTOR_PATH); put("web.http.management.port", String.valueOf(PLATO_MANAGEMENT_PORT)); put("web.http.management.path", PLATO_MANAGEMENT_PATH); - put("web.http.ids.port", String.valueOf(PLATO_IDS_API_PORT)); - put("web.http.ids.path", IDS_PATH); + put("web.http.protocol.port", String.valueOf(PLATO_DSP_API_PORT)); + put("web.http.protocol.path", DSP_PATH); + put("edc.dsp.callback.address", PLATO_DSP_CALLBACK); put("edc.api.auth.key", "testkey"); - put("ids.webhook.address", PLATO_IDS_API); put("web.http.public.port", PLATO_PUBLIC_API_PORT); put("web.http.public.path", "/api/public"); // embedded dataplane config @@ -100,6 +107,7 @@ public class MultiRuntimeTest { put("edc.dataplane.selector.httpplane.destinationtypes", "HttpProxy"); put("edc.dataplane.selector.httpplane.properties", "{\"publicApiUrl\":\"http://localhost:" + PLATO_PUBLIC_API_PORT + "/api/public\"}"); put("tractusx.businesspartnervalidation.log.agreement.validation", "true"); + put("edc.agent.identity.key", "BusinessPartnerNumber"); } }); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java index bd1546111..e2546035f 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java @@ -14,28 +14,27 @@ package org.eclipse.tractusx.edc.lifecycle; +import com.fasterxml.jackson.databind.ObjectMapper; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.api.model.IdResponseDto; -import org.eclipse.edc.api.query.QuerySpecDto; -import org.eclipse.edc.catalog.spi.Catalog; -import org.eclipse.edc.connector.api.management.catalog.model.CatalogRequestDto; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractNegotiationDto; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; -import org.eclipse.edc.connector.api.management.contractnegotiation.model.NegotiationInitiateRequestDto; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferRequestDto; -import org.eclipse.edc.connector.policy.spi.PolicyDefinition; +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.PolicyRegistrationTypes; -import org.eclipse.edc.spi.asset.AssetSelectorExpression; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.injection.InjectionContainer; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.helpers.AssetHelperFunctions; +import org.eclipse.tractusx.edc.helpers.ContractDefinitionHelperFunctions; import org.eclipse.tractusx.edc.token.MockDapsService; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -43,8 +42,6 @@ import java.net.URI; import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -53,31 +50,45 @@ import static io.restassured.http.ContentType.JSON; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThan; +import static org.awaitility.Awaitility.await; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.DCAT_DATASET_ATTRIBUTE; +import static org.eclipse.tractusx.edc.helpers.AssetHelperFunctions.createDataAddressBuilder; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.createCatalogRequest; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetAssetId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetContractId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetFirstPolicy; +import static org.eclipse.tractusx.edc.helpers.ContractNegotiationHelperFunctions.createNegotiationRequest; +import static org.eclipse.tractusx.edc.helpers.EdrNegotiationHelperFunctions.createEdrNegotiationRequest; +import static org.eclipse.tractusx.edc.helpers.TransferProcessHelperFunctions.createTransferRequest; +import static org.mockito.Mockito.mock; public class Participant extends EdcRuntimeExtension implements BeforeAllCallback, AfterAllCallback { private final String managementUrl; private final String apiKey; - private final String idsEndpoint; + private final String dspEndpoint; private final TypeManager typeManager = new TypeManager(); - private final String idsId; + private final String runtimeName; private final String bpn; private final String backend; + private final JsonLd jsonLd; + private final Duration timeout = Duration.ofSeconds(30); + + private final ObjectMapper objectMapper = JacksonJsonLd.createObjectMapper(); + private DataWiper wiper; - public Participant(String moduleName, String runtimeName, Map properties) { + public Participant(String moduleName, String runtimeName, String bpn, Map properties) { super(moduleName, runtimeName, properties); this.managementUrl = URI.create(format("http://localhost:%s%s", properties.get("web.http.management.port"), properties.get("web.http.management.path"))).toString(); - this.idsEndpoint = URI.create(format("http://localhost:%s%s", properties.get("web.http.ids.port"), properties.get("web.http.ids.path"))).toString(); + this.dspEndpoint = URI.create(format("http://localhost:%s%s", properties.get("web.http.protocol.port"), properties.get("web.http.protocol.path"))).toString(); this.apiKey = properties.get("edc.api.auth.key"); - this.idsId = properties.get("edc.ids.id"); - this.bpn = runtimeName + "-BPN"; + this.bpn = bpn; + this.runtimeName = runtimeName; this.backend = properties.get("edc.receiver.http.dynamic.endpoint"); this.registerServiceMock(IdentityService.class, new MockDapsService(getBpn())); - + jsonLd = new TitaniumJsonLd(mock(Monitor.class)); typeManager.registerTypes(PolicyRegistrationTypes.TYPES.toArray(Class[]::new)); } @@ -106,52 +117,27 @@ public void afterAll(ExtensionContext context) throws Exception { /** * Creates an asset with the given ID and props using the participant's Data Management API */ - public void createAsset(String id, Map properties) { - properties = new HashMap<>(properties); - properties.put("asset:prop:id", id); - properties.put("asset:prop:description", "test description"); - - var asset = Map.of( - "asset", Map.of( - "id", id, - "properties", properties - ), - "dataAddress", Map.of( - "properties", Map.of("type", "test-type") - - ) - ); - - baseRequest() - .body(asset) - .when() - .post("/assets") - .then() - .statusCode(200) - .contentType(JSON); + public void createAsset(String id, JsonObject properties) { + createAsset(id, properties, createDataAddressBuilder("test-type").build()); + } + /** + * Creates an asset with the given ID and props using the participant's Data Management API + */ + public void createAsset(String id) { + createAsset(id, Json.createObjectBuilder().build(), createDataAddressBuilder("test-type").build()); } /** * Creates an asset with the given ID and props using the participant's Data Management API */ - public void createAsset(String id, Map asserProperties, HttpDataAddress address) { - asserProperties = new HashMap<>(asserProperties); - asserProperties.put("asset:prop:id", id); - asserProperties.put("asset:prop:description", "test description"); - - var asset = Map.of( - "asset", Map.of( - "id", id, - "properties", asserProperties - ), - "dataAddress", address - ); + public void createAsset(String id, JsonObject assetProperties, JsonObject dataAddress) { + var asset = AssetHelperFunctions.createAsset(id, assetProperties, dataAddress); baseRequest() .body(asset) .when() - .post("/assets") + .post("/v2/assets") .then() .statusCode(200) .contentType(JSON); @@ -160,110 +146,94 @@ public void createAsset(String id, Map asserProperties, HttpData /** * Creates a {@link org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition} using the participant's Data Management API */ - public void createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId, long contractValidityDurationSeconds) { - var contractDefinition = Map.of( - "id", definitionId, - "accessPolicyId", accessPolicyId, - "validity", String.valueOf(contractValidityDurationSeconds), - "contractPolicyId", contractPolicyId, - "criteria", AssetSelectorExpression.Builder.newInstance().constraint("asset:prop:id", "=", assetId).build().getCriteria() - ); + public void createContractDefinition(String assetId, String definitionId, String accessPolicyId, String contractPolicyId) { + var requestBody = ContractDefinitionHelperFunctions.createContractDefinition(assetId, definitionId, accessPolicyId, contractPolicyId); baseRequest() - .body(contractDefinition) + .contentType(JSON) + .body(requestBody) .when() - .post("/contractdefinitions") + .post("/v2/contractdefinitions") .then() .statusCode(200) - .contentType(JSON).contentType(JSON); + .contentType(JSON); } - /** - * Creates a {@link PolicyDefinition} using the participant's Data Management API - */ - public void createPolicy(PolicyDefinition policyDefinition) { + public void createPolicy(JsonObject policyDefinition) { baseRequest() + .contentType(JSON) .body(policyDefinition) .when() - .post("/policydefinitions") + .post("/v2/policydefinitions") .then() .statusCode(200) - .contentType(JSON).contentType(JSON); + .contentType(JSON); } - /** - * Requests the {@link Catalog} from another participant using this participant's Data Management API - */ - public Catalog requestCatalog(Participant other) { - return requestCatalog(other, QuerySpecDto.Builder.newInstance().build()); - } + public String negotiateContract(Participant other, String assetId) { + var dataset = getDatasetForAsset(other, assetId); + assertThat(dataset).withFailMessage("Catalog received from " + other.runtimeName + " was empty!").isNotEmpty(); - /** - * Requests the {@link Catalog} from another participant using this participant's Data Management API - */ - public Catalog requestCatalog(Participant other, QuerySpecDto query) { + var policy = getDatasetFirstPolicy(dataset); + var contractId = getDatasetContractId(dataset); + var requestBody = createNegotiationRequest(other.dspEndpoint, other.getBpn(), contractId.toString(), contractId.assetIdPart(), policy); var response = baseRequest() .when() - .body(CatalogRequestDto.Builder.newInstance() - .providerUrl(other.idsEndpoint + "/data") - .querySpec(query) - .build()) - .post("/catalog/request") + .body(requestBody) + .post("/v2/contractnegotiations") .then(); - var code = response.extract().statusCode(); var body = response.extract().body().asString(); + assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - // doing an assertJ style assertion will allow us to use the body as fail message if the return code != 200 - assertThat(code).withFailMessage(body).isEqualTo(200); - return typeManager.readValue(body, Catalog.class); + return response.extract().jsonPath().getString(ID); } - public String negotiateContract(Participant other, String assetId) { - var catalog = requestCatalog(other); - assertThat(catalog.getContractOffers()).withFailMessage("Catalog received from " + other.idsId + " was empty!").isNotEmpty(); + public void negotiateEdr(Participant other, String assetId, JsonArray callbacks) { + var dataset = getDatasetForAsset(other, assetId); + assertThat(dataset).withFailMessage("Catalog received from " + other.runtimeName + " was empty!").isNotEmpty(); + + var policy = getDatasetFirstPolicy(dataset); + var contractId = getDatasetContractId(dataset); + + var requestBody = createEdrNegotiationRequest(other.dspEndpoint, other.getBpn(), contractId.toString(), contractId.assetIdPart(), policy, callbacks); + + var response = baseRequest() .when() - .body(NegotiationInitiateRequestDto.Builder.newInstance() - .connectorAddress(other.idsEndpoint + "/data") - .connectorId(getBpn()) - .offer(catalog.getContractOffers().stream().filter(o -> o.getAsset().getId().equals(assetId)) - .findFirst().map(co -> ContractOfferDescription.Builder.newInstance() - .assetId(assetId) - .offerId(co.getId()) - .policy(co.getPolicy()) - .validity(ChronoUnit.SECONDS.between(co.getContractStart(), co.getContractEnd().plus(Duration.ofMillis(500)))) // the plus 1 is required due to https://github.com/eclipse-edc/Connector/issues/2650 - .build()) - .orElseThrow((() -> new RuntimeException("A contract for assetId " + assetId + " could not be negotiated")))) - .build() - ) - .post("/contractnegotiations") + .body(requestBody) + .post("/adapter/edrs") .then(); var body = response.extract().body().asString(); assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, IdResponseDto.class).getId(); } - public ContractNegotiationDto getNegotiation(String negotiationId) { - var response = baseRequest() + public String getNegotiationState(String negotiationId) { + return baseRequest() .when() - .get("/contractnegotiations/" + negotiationId) - .then(); + .get("/v2/contractnegotiations/{id}/state", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); + } - var body = response.extract().body().asString(); - assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, ContractNegotiationDto.class); + public String getContractAgreementId(String negotiationId) { + return getContractNegotiationField(negotiationId, "contractAgreementId"); } - /** - * Returns this participant's IDS ID - */ - public String idsId() { - return idsId; + private String getContractNegotiationField(String negotiationId, String fieldName) { + return baseRequest() + .when() + .get("/v2/contractnegotiations/{id}", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath() + .getString(format("'edc:%s'", fieldName)); } + /** * Returns this participant's BusinessPartnerNumber (=BPN). This is constructed of the runtime name plus "-BPN" */ @@ -271,38 +241,29 @@ public String getBpn() { return bpn; } - public String requestTransfer(String contractId, String assetId, Participant other, DataAddress destination, String dataRequestId) { + public String requestTransfer(String dataRequestId, String contractId, String assetId, Participant other, JsonObject destination) { + + var request = createTransferRequest(dataRequestId, other.dspEndpoint, contractId, assetId, false, destination); var response = baseRequest() .when() - .body(TransferRequestDto.Builder.newInstance() - .assetId(assetId) - .id(dataRequestId) - .connectorAddress(other.idsEndpoint + "/data") - .managedResources(false) - .contractId(contractId) - .connectorId(bpn) - .protocol("ids-multipart") - .dataDestination(destination) - .build()) - .post("/transferprocess") + .body(request) + .post("/v2/transferprocesses") .then(); var body = response.extract().body().asString(); assertThat(response.extract().statusCode()).withFailMessage(body).isBetween(200, 299); - return typeManager.readValue(body, IdResponseDto.class).getId(); + return response.extract().jsonPath().getString(ID); + } - public TransferProcessDto getTransferProcess(String transferProcessId) { - var json = baseRequest() + public String getTransferProcessState(String id) { + return baseRequest() .when() - .get("/transferprocess/" + transferProcessId) + .get("/v2/transferprocesses/{id}/state", id) .then() - .statusCode(allOf(greaterThanOrEqualTo(200), lessThan(300))) - .extract().body().asString(); - - return typeManager.readValue(json, TransferProcessDto.class); - + .statusCode(200) + .extract().body().jsonPath().getString("'edc:state'"); } public EndpointDataReference getDataReference(String dataRequestId) { @@ -332,6 +293,48 @@ public String pullData(EndpointDataReference edr, Map queryParam return response.body().asString(); } + public JsonArray getCatalogDatasets(Participant provider) { + return getCatalogDatasets(provider, null); + } + + public JsonArray getCatalogDatasets(Participant provider, JsonObject querySpec) { + var datasetReference = new AtomicReference(); + + var requestBody = createCatalogRequest(querySpec, provider.dspEndpoint); + + await().atMost(timeout).untilAsserted(() -> { + var response = baseRequest() + .contentType(JSON) + .when() + .body(requestBody) + .post("/v2/catalog/request") + .then() + .statusCode(200) + .extract().body().asString(); + + var responseBody = objectMapper.readValue(response, JsonObject.class); + + var catalog = jsonLd.expand(responseBody).orElseThrow(f -> new EdcException(f.getFailureDetail())); + + var datasets = catalog.getJsonArray(DCAT_DATASET_ATTRIBUTE); + assertThat(datasets).hasSizeGreaterThan(0); + + datasetReference.set(datasets); + }); + + return datasetReference.get(); + } + + public JsonObject getDatasetForAsset(Participant provider, String assetId) { + var datasets = getCatalogDatasets(provider); + return datasets.stream() + .map(JsonValue::asJsonObject) + .filter(it -> assetId.equals(getDatasetAssetId(it))) + .findFirst() + .orElseThrow(() -> new EdcException(format("No dataset for asset %s in the catalog", assetId))); + } + + @Override protected void bootExtensions(ServiceExtensionContext context, List> serviceExtensions) { super.bootExtensions(context, serviceExtensions); diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java index f865d39d9..234de574e 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java @@ -19,25 +19,27 @@ class TestRuntimeConfiguration { - static final String IDS_PATH = "/api/v1/ids"; + static final String DSP_PATH = "/api/v1/dsp"; static final int PLATO_CONNECTOR_PORT = getFreePort(); static final int PLATO_MANAGEMENT_PORT = getFreePort(); static final String PLATO_CONNECTOR_PATH = "/api"; static final String PLATO_MANAGEMENT_PATH = "/api/v1/management"; - static final int PLATO_IDS_API_PORT = getFreePort(); - static final String PLATO_IDS_API = "http://localhost:" + PLATO_IDS_API_PORT; + + static final int PLATO_DSP_API_PORT = getFreePort(); + + static final String PLATO_DSP_CALLBACK = "http://localhost:" + PLATO_DSP_API_PORT + DSP_PATH; static final int SOKRATES_CONNECTOR_PORT = getFreePort(); static final int SOKRATES_MANAGEMENT_PORT = getFreePort(); static final String SOKRATES_CONNECTOR_PATH = "/api"; static final String SOKRATES_MANAGEMENT_PATH = "/api/v1/management"; - static final int SOKRATES_IDS_API_PORT = getFreePort(); - static final String SOKRATES_IDS_API = "http://localhost:" + SOKRATES_IDS_API_PORT; - + static final int SOKRATES_DSP_API_PORT = getFreePort(); + static final String SOKRATES_DSP_CALLBACK = "http://localhost:" + SOKRATES_DSP_API_PORT + DSP_PATH; static final String SOKRATES_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); static final String SOKRATES_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); + } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java deleted file mode 100644 index 56d92afbe..000000000 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/policy/PolicyHelperFunctions.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.tractusx.edc.policy; - - -import org.eclipse.edc.connector.policy.spi.PolicyDefinition; -import org.eclipse.edc.policy.model.Action; -import org.eclipse.edc.policy.model.AtomicConstraint; -import org.eclipse.edc.policy.model.Constraint; -import org.eclipse.edc.policy.model.LiteralExpression; -import org.eclipse.edc.policy.model.Operator; -import org.eclipse.edc.policy.model.OrConstraint; -import org.eclipse.edc.policy.model.Permission; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.policy.model.PolicyType; - -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class PolicyHelperFunctions { - /** - * Creates a {@link PolicyDefinition} using the given ID, that contains equality constraints for each of the given BusinessPartnerNumbers: - * each BPN is converted into an {@link AtomicConstraint} {@code BusinessPartnerNumber EQ [BPN]}. - */ - public static PolicyDefinition businessPartnerNumberPolicy(String id, String... bpns) { - - var bpnConstraints = Stream.of(bpns) - .map(bpn -> (Constraint) AtomicConstraint.Builder.newInstance() - .leftExpression(new LiteralExpression("BusinessPartnerNumber")) - .operator(Operator.EQ) - .rightExpression(new LiteralExpression(bpn)) - .build()) - .collect(Collectors.toList()); - - return PolicyDefinition.Builder.newInstance() - .id(id) - .policy(Policy.Builder.newInstance() - .permission(Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("USE").build()) - .constraint(OrConstraint.Builder.newInstance() - .constraints(bpnConstraints) - .build()) - .build()).build()).build(); - } - - public static PolicyDefinition noConstraintPolicy(String id) { - return PolicyDefinition.Builder.newInstance() - .id(id) - .policy(Policy.Builder.newInstance() - .permission(Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("USE").build()) - .build()) - .type(PolicyType.SET) - .build()) - .build(); - } -} diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java index ec8045f5b..1a9149099 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/CatalogTest.java @@ -15,18 +15,18 @@ package org.eclipse.tractusx.edc.tests; -import org.eclipse.edc.api.query.QuerySpecDto; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Map; - import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.noConstraintPolicy; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetAssetId; +import static org.eclipse.tractusx.edc.helpers.CatalogHelperFunctions.getDatasetPolicies; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.noConstraintPolicyDefinition; +import static org.eclipse.tractusx.edc.helpers.QueryHelperFunctions.createQuery; @EndToEndTest public class CatalogTest extends MultiRuntimeTest { @@ -34,23 +34,21 @@ public class CatalogTest extends MultiRuntimeTest { @Test void requestCatalog_fulfillsPolicy_shouldReturnOffer() { // arrange - sokrates.createAsset("test-asset", Map.of("fooprop", "fooval")); - var accessPolicy = noConstraintPolicy("test-ap1"); - var contractPolicy = noConstraintPolicy("test-cp1"); + sokrates.createAsset("test-asset"); + var accessPolicy = noConstraintPolicyDefinition("test-ap1"); + var contractPolicy = noConstraintPolicyDefinition("test-cp1"); sokrates.createPolicy(accessPolicy); sokrates.createPolicy(contractPolicy); - sokrates.createContractDefinition("test-asset", "test-def", "test-ap1", "test-cp1", 60); + sokrates.createContractDefinition("test-asset", "test-def", "test-ap1", "test-cp1"); // act - var catalog = plato.requestCatalog(sokrates); + var catalog = plato.getCatalogDatasets(sokrates); // assert - assertThat(catalog.getContractOffers()).isNotEmpty() + assertThat(catalog).isNotEmpty() .hasSize(1) .allSatisfy(co -> { - assertThat(co.getAsset().getId()).isEqualTo("test-asset"); - assertThat(co.getProvider().toString()).isEqualTo(sokrates.idsId()); - assertThat(co.getConsumer().toString()).isEqualTo(plato.idsId()); + assertThat(getDatasetAssetId(co)).isEqualTo("test-asset"); }); } @@ -58,66 +56,71 @@ void requestCatalog_fulfillsPolicy_shouldReturnOffer() { @Test @DisplayName("Verify that Plato receives only the offers he is permitted to") void requestCatalog_filteredByBpn_shouldReject() { - var onlyPlatoPolicy = businessPartnerNumberPolicy("ap", "BPN1", "BPN2", plato.getBpn()); - var onlyDiogenesPolicy = businessPartnerNumberPolicy("dp", "ARISTOTELES-BPN"); + var onlyPlatoId = "ap"; + var onlyDiogenesId = "db"; + + var onlyPlatoPolicy = businessPartnerNumberPolicy(onlyPlatoId, "BPN1", "BPN2", plato.getBpn()); + var onlyDiogenesPolicy = businessPartnerNumberPolicy(onlyDiogenesId, "ARISTOTELES-BPN"); var noConstraintPolicyId = "no-constraint"; sokrates.createPolicy(onlyPlatoPolicy); - sokrates.createPolicy(noConstraintPolicy(noConstraintPolicyId)); + sokrates.createPolicy(onlyDiogenesPolicy); + sokrates.createPolicy(noConstraintPolicyDefinition(noConstraintPolicyId)); - sokrates.createAsset("test-asset1", Map.of("canSee", "true")); - sokrates.createAsset("test-asset2", Map.of("canSee", "true")); - sokrates.createAsset("test-asset3", Map.of("canSee", "false")); + sokrates.createAsset("test-asset1"); + sokrates.createAsset("test-asset2"); + sokrates.createAsset("test-asset3"); - sokrates.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId, 60); - sokrates.createContractDefinition("test-asset2", "def2", onlyPlatoPolicy.getId(), noConstraintPolicyId, 60); - sokrates.createContractDefinition("test-asset3", "def3", onlyDiogenesPolicy.getId(), noConstraintPolicyId, 60); + sokrates.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId); + sokrates.createContractDefinition("test-asset2", "def2", onlyPlatoId, noConstraintPolicyId); + sokrates.createContractDefinition("test-asset3", "def3", onlyDiogenesId, noConstraintPolicyId); // act - var catalog = plato.requestCatalog(sokrates); - assertThat(catalog.getContractOffers()).hasSize(2); + var catalog = plato.getCatalogDatasets(sokrates); + assertThat(catalog).hasSize(2); } @Test @DisplayName("Multiple ContractDefinitions exist for one Asset") void requestCatalog_multipleOffersForAsset() { - sokrates.createAsset("asset-1", Map.of("test-key", "test-val")); - sokrates.createPolicy(noConstraintPolicy("policy-1")); + sokrates.createAsset("asset-1"); + sokrates.createPolicy(noConstraintPolicyDefinition("policy-1")); sokrates.createPolicy(businessPartnerNumberPolicy("policy-2", plato.getBpn())); - sokrates.createContractDefinition("asset-1", "def1", "policy-1", "policy-1", 60); - sokrates.createContractDefinition("asset-1", "def2", "policy-2", "policy-1", 60); + sokrates.createContractDefinition("asset-1", "def1", "policy-1", "policy-1"); + sokrates.createContractDefinition("asset-1", "def2", "policy-2", "policy-1"); - var catalog = plato.requestCatalog(sokrates); - assertThat(catalog.getContractOffers()).hasSize(2) - .allSatisfy(cd -> assertThat(cd.getAsset().getId()).isEqualTo("asset-1")) - // .hasToString is advisable as it handles NPEs better: - .allSatisfy(cd -> assertThat(cd.getConsumer()).hasToString(plato.idsId())) - .allSatisfy(cd -> assertThat(cd.getProvider()).hasToString(sokrates.idsId())); + var catalog = plato.getCatalogDatasets(sokrates); + assertThat(catalog).hasSize(1) + .allSatisfy(cd -> { + assertThat(getDatasetAssetId(cd)).isEqualTo("asset-1"); + assertThat(getDatasetPolicies(cd)).hasSize(2); + }); } @Test @DisplayName("Catalog with 1000 offers") void requestCatalog_of1000Assets_shouldContainAll() { - var policy = businessPartnerNumberPolicy("policy-1", plato.getBpn()); + var policyId = "policy-1"; + var policy = businessPartnerNumberPolicy(policyId, plato.getBpn()); sokrates.createPolicy(policy); - sokrates.createPolicy(noConstraintPolicy("noconstraint")); + sokrates.createPolicy(noConstraintPolicyDefinition("noconstraint")); range(0, 1000) .forEach(i -> { var assetId = "asset-" + i; - sokrates.createAsset(assetId, Map.of()); - sokrates.createContractDefinition(assetId, "def-" + i, policy.getId(), "noconstraint", 60); + sokrates.createAsset(assetId); + sokrates.createContractDefinition(assetId, "def-" + i, policyId, "noconstraint"); }); // request all at once - var o = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(1000).offset(0).build()).getContractOffers(); - assertThat(o).hasSize(1000); + var dataset = plato.getCatalogDatasets(sokrates, createQuery(1000, 0)); + assertThat(dataset).hasSize(1000); // request in chunks - var o2 = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(500).offset(0).build()).getContractOffers(); - var o3 = plato.requestCatalog(sokrates, QuerySpecDto.Builder.newInstance().limit(500).offset(500).build()).getContractOffers(); + var o2 = plato.getCatalogDatasets(sokrates, createQuery(500, 0)); + var o3 = plato.getCatalogDatasets(sokrates, createQuery(500, 500)); assertThat(o2).doesNotContainAnyElementsOf(o3); } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java index 1f93ae5e7..7ab0419e9 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/HttpConsumerPullWithProxyTest.java @@ -14,14 +14,12 @@ package org.eclipse.tractusx.edc.tests; +import jakarta.json.Json; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import org.eclipse.edc.connector.api.management.transferprocess.model.TransferProcessDto; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.spi.types.domain.HttpDataAddress; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; import org.junit.jupiter.api.AfterEach; @@ -37,14 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; -import static org.eclipse.edc.connector.transfer.dataplane.spi.TransferDataPlaneConstants.HTTP_PROXY; -import static org.eclipse.tractusx.edc.policy.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; +import static org.eclipse.tractusx.edc.helpers.TransferProcessHelperFunctions.createProxyRequest; @EndToEndTest public class HttpConsumerPullWithProxyTest extends MultiRuntimeTest { private static final Duration ASYNC_TIMEOUT = ofSeconds(45); private static final Duration ASYNC_POLL_INTERVAL = ofSeconds(1); - private final long ONE_WEEK = 60 * 60 * 24 * 7; MockWebServer server = new MockWebServer(); @Test @@ -55,15 +53,19 @@ void transferData_privateBackend() throws IOException, InterruptedException { var authCodeHeaderName = "test-authkey"; var authCode = "test-authcode"; - plato.createAsset(assetId, Map.of(), HttpDataAddress.Builder.newInstance() - .contentType("application/json") - .baseUrl(url.toString()) - .authKey(authCodeHeaderName) - .authCode(authCode) - .build()); + var dataAddress = Json.createObjectBuilder() + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "contentType", "application/json") + .add(EDC_NAMESPACE + "baseUrl", url.toString()) + .add(EDC_NAMESPACE + "authKey", authCodeHeaderName) + .add(EDC_NAMESPACE + "authCode", authCode) + .build(); + + plato.createAsset(assetId, Json.createObjectBuilder().build(), dataAddress); + plato.createPolicy(businessPartnerNumberPolicy("policy-1", sokrates.getBpn())); plato.createPolicy(businessPartnerNumberPolicy("policy-2", sokrates.getBpn())); - plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2", ONE_WEEK); + plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2"); var negotiationId = sokrates.negotiateContract(plato, assetId); // forward declarations of our actual values @@ -72,18 +74,19 @@ void transferData_privateBackend() throws IOException, InterruptedException { var contractAgreementId = new AtomicReference(); var edr = new AtomicReference(); - // wait for the successful contract negotiation await().pollInterval(ASYNC_POLL_INTERVAL) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - var negotiation = sokrates.getNegotiation(negotiationId); - assertThat(negotiation.getState()).isEqualTo(ContractNegotiationStates.CONFIRMED.toString()); - contractAgreementId.set(negotiation.getContractAgreementId()); - assertThat(contractAgreementId).isNotNull(); - transferProcessId.set(sokrates.requestTransfer(contractAgreementId.get(), assetId, plato, DataAddress.Builder.newInstance() - .type(HTTP_PROXY) - .build(), dataRequestId)); + var negotiationState = sokrates.getNegotiationState(negotiationId); + assertThat(negotiationState).isEqualTo(ContractNegotiationStates.FINALIZED.toString()); + + var agreementId = sokrates.getContractAgreementId(negotiationId); + assertThat(agreementId).isNotNull(); + contractAgreementId.set(agreementId); + + var tpId = sokrates.requestTransfer(dataRequestId, contractAgreementId.get(), assetId, plato, createProxyRequest()); + transferProcessId.set(tpId); assertThat(transferProcessId).isNotNull(); }); @@ -91,9 +94,8 @@ void transferData_privateBackend() throws IOException, InterruptedException { await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - var tp = sokrates.getTransferProcess(transferProcessId.get()); - assertThat(tp).isNotNull() - .extracting(TransferProcessDto::getState).isEqualTo(TransferProcessStates.COMPLETED.toString()); + var tpState = sokrates.getTransferProcessState(transferProcessId.get()); + assertThat(tpState).isNotNull().isEqualTo(TransferProcessStates.COMPLETED.toString()); }); // wait until EDC is available on the consumer side diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java new file mode 100644 index 000000000..b10b4fa8a --- /dev/null +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/NegotiateEdrTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.tests; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.json.Json; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationInitiated; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationRequested; +import org.eclipse.edc.connector.contract.spi.event.contractnegotiation.ContractNegotiationVerified; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessCompleted; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessInitiated; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessProvisioned; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessRequested; +import org.eclipse.edc.connector.transfer.spi.event.TransferProcessStarted; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.tractusx.edc.lifecycle.MultiRuntimeTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.helpers.EdrNegotiationHelperFunctions.createCallback; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.businessPartnerNumberPolicy; + +@EndToEndTest +public class NegotiateEdrTest extends MultiRuntimeTest { + + MockWebServer server = new MockWebServer(); + + ObjectMapper mapper = new ObjectMapper(); + + + @Test + @DisplayName("Verify that the callbacks are invoked when negotiating an EDR") + void negotiateEdr_shouldInvokeCallbacks() throws IOException { + + var expectedEvents = List.of( + createEvent(ContractNegotiationInitiated.class), + createEvent(ContractNegotiationRequested.class), + createEvent(ContractNegotiationAgreed.class), + createEvent(ContractNegotiationFinalized.class), + createEvent(ContractNegotiationVerified.class), + createEvent(TransferProcessInitiated.class), + createEvent(TransferProcessProvisioned.class), + createEvent(TransferProcessRequested.class), + createEvent(TransferProcessStarted.class), + createEvent(TransferProcessCompleted.class)); + + var assetId = "api-asset-1"; + var url = server.url("/mock/api"); + server.start(); + + var authCodeHeaderName = "test-authkey"; + var authCode = "test-authcode"; + plato.createAsset(assetId, Json.createObjectBuilder().build(), Json.createObjectBuilder() + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "contentType", "application/json") + .add(EDC_NAMESPACE + "baseUrl", url.toString()) + .add(EDC_NAMESPACE + "authKey", authCodeHeaderName) + .add(EDC_NAMESPACE + "authCode", authCode) + .build()); + + plato.createPolicy(businessPartnerNumberPolicy("policy-1", sokrates.getBpn())); + plato.createPolicy(businessPartnerNumberPolicy("policy-2", sokrates.getBpn())); + plato.createContractDefinition(assetId, "def-1", "policy-1", "policy-2"); + + + expectedEvents.forEach(_event -> { + server.enqueue(new MockResponse()); + }); + + var callbacks = Json.createArrayBuilder() + .add(createCallback(url.toString(), true, Set.of("contract.negotiation", "transfer.process"))) + .build(); + + sokrates.negotiateEdr(plato, assetId, callbacks); + + var events = expectedEvents.stream() + .map(this::waitForEvent) + .collect(Collectors.toList()); + + assertThat(expectedEvents).usingRecursiveFieldByFieldElementComparator().containsAll(events); + } + + ReceivedEvent createEvent(Class klass) { + return ReceivedEvent.Builder.newInstance().type(klass.getSimpleName()).build(); + } + + ReceivedEvent waitForEvent(ReceivedEvent event) { + try { + var request = server.takeRequest(20, TimeUnit.SECONDS); + if (request != null) { + return mapper.readValue(request.getBody().inputStream(), ReceivedEvent.class); + } else { + throw new RuntimeException("Timeout exceeded waiting for events"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class ReceivedEvent { + private String type; + + public String getType() { + return type; + } + + @Override + public String toString() { + return "ReceivedEvent{" + + "type='" + type + '\'' + + '}'; + } + + public static class Builder { + private final ReceivedEvent event; + + private Builder(ReceivedEvent event) { + this.event = event; + } + + public static Builder newInstance() { + return new Builder(new ReceivedEvent()); + } + + public Builder type(String type) { + this.event.type = type; + return this; + } + + public ReceivedEvent build() { + return event; + } + } + + + } +} diff --git a/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts new file mode 100644 index 000000000..50bd6598d --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + testImplementation(libs.edc.junit) + testImplementation(libs.restAssured) + testImplementation(libs.okhttp.mockwebserver) + + // test runtime config + testImplementation(libs.edc.config.filesystem) + testImplementation(libs.edc.vault.filesystem) + testImplementation(libs.edc.dpf.http) + testImplementation(project(":spi:edr-cache-spi")) + testImplementation(project(":core:edr-cache-core")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-consumer-api")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-provider-api")) + testImplementation(project(":edc-dataplane:edc-dataplane-proxy-provider-core")) + +} + + + diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java new file mode 100644 index 000000000..c7649ea67 --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/DpfProxyEndToEndTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.e2e; + +import io.restassured.specification.RequestSpecification; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceCache; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static java.lang.String.format; +import static java.lang.String.valueOf; +import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.EdrCacheSetup.createEntries; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.KeyStoreSetup.createKeyStore; +import static org.eclipse.tractusx.edc.dataplane.proxy.e2e.VaultSetup.createVaultStore; +import static org.hamcrest.Matchers.is; + + +/** + * Performs end-to-end testing using a consumer data plane, a producer data plane, and a proxied HTTP endpoint. + *

+ * The consumer runtime is configured with three EDRs: + *

+ *

+ * The end-to-end tests verify asset content is correctly proxied from the HTTP endpoint, error messages from the HTTP endpoint are correctly propagated, + * and invalid requests are properly handled. + *

+ * This test can be executed using the Gradle or JUnit test runners. + */ +@EndToEndTest +public class DpfProxyEndToEndTest { + private static final String LAUNCHER_MODULE = ":edc-tests:edc-dataplane-proxy-e2e"; + + private static final int CONSUMER_HTTP_PORT = getFreePort(); + private static final int CONSUMER_PROXY_PORT = getFreePort(); + private static final int PRODUCER_HTTP_PORT = getFreePort(); + private static final int MOCK_ENDPOINT_PORT = getFreePort(); + + private static final String PROXY_SUBPATH = "proxy/aas/request"; + + private static final String SINGLE_TRANSFER_ID = "5355d524-2616-43df-9096-558afffff659"; + private static final String SINGLE_ASSET_ID = "79f13b89-59a6-4278-8c8e-8540849dbab8"; + private static final String MULTI_ASSET_ID = "9260f395-3d94-4b8b-bdaa-941ead596ce5"; + + private static final String REQUEST_TEMPLATE_TP = "{\"transferProcessId\": \"%s\", \"endpointUrl\" : \"http://localhost:%s/api/gateway/aas/test\"}"; + private static final String REQUEST_TEMPLATE_ASSET = "{\"assetId\": \"%s\", \"endpointUrl\" : \"http://localhost:%s/api/gateway/aas/test\"}"; + + private static final String MOCK_ENDPOINT_200_BODY = "{\"message\":\"test\"}"; + + public static final String KEYSTORE_PASS = "test123"; + + private MockWebServer mockEndpoint; + + @RegisterExtension + static EdcRuntimeExtension CONSUMER = new EdcRuntimeExtension( + LAUNCHER_MODULE, + "consumer", + baseConfig(Map.of( + "web.http.port", valueOf(CONSUMER_HTTP_PORT), + "tx.dpf.consumer.proxy.port", valueOf(CONSUMER_PROXY_PORT) + ))); + + @RegisterExtension + static EdcRuntimeExtension PROVIDER = new EdcRuntimeExtension( + LAUNCHER_MODULE, + "provider", + baseConfig(Map.of( + "web.http.port", valueOf(PRODUCER_HTTP_PORT), + "tx.dpf.proxy.gateway.aas.proxied.path", "http://localhost:" + MOCK_ENDPOINT_PORT + ))); + + @BeforeEach + void setUp() { + mockEndpoint = new MockWebServer(); + } + + @AfterEach + void tearDown() throws IOException { + if (mockEndpoint != null) { + mockEndpoint.close(); + } + } + + @Test + void verify_end2EndFlows() throws IOException { + + seedEdrCache(); + + // set up the HTTP endpoint + mockEndpoint.enqueue(new MockResponse().setBody(MOCK_ENDPOINT_200_BODY)); + mockEndpoint.enqueue(new MockResponse().setBody(MOCK_ENDPOINT_200_BODY)); + mockEndpoint.enqueue(new MockResponse().setResponseCode(404)); + mockEndpoint.enqueue(new MockResponse().setResponseCode(401)); + mockEndpoint.start(MOCK_ENDPOINT_PORT); + + var tpSpec = createSpecification(format(REQUEST_TEMPLATE_TP, SINGLE_TRANSFER_ID, PRODUCER_HTTP_PORT)); + + // verify content successfully proxied using a transfer process id + tpSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(200) + .assertThat().body(is(MOCK_ENDPOINT_200_BODY)); + + // verify content successfully proxied using an asset id for the case where only one active transfer process exists for the asset + var assetSpec = createSpecification(format(REQUEST_TEMPLATE_ASSET, SINGLE_ASSET_ID, PRODUCER_HTTP_PORT)); + assetSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(200) + .assertThat().body(is(MOCK_ENDPOINT_200_BODY)); + + // verify content not found (404) response at the endpoint is propagated + tpSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(404); + + // verify unauthorized response (403) at the endpoint is propagated + tpSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(401); + + // verify EDR not found results in a bad request response (400) + var invalidSpec = createSpecification(format(REQUEST_TEMPLATE_TP, "123", PRODUCER_HTTP_PORT)); + invalidSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(400); + + // verify more than one contract for the same asset results in a precondition required response (428) + var multiAssetSpec = createSpecification(format(REQUEST_TEMPLATE_ASSET, MULTI_ASSET_ID, PRODUCER_HTTP_PORT)); + multiAssetSpec.with() + .post(PROXY_SUBPATH) + .then() + .assertThat().statusCode(428); + } + + private RequestSpecification createSpecification(String body) { + return given() + .baseUri("http://localhost:" + CONSUMER_PROXY_PORT) + .contentType("application/json") + .body(body); + } + + private static Map baseConfig(Map values) { + var map = new HashMap<>(values); + map.put("edc.vault", createVaultStore()); + map.put("edc.keystore", createKeyStore(KEYSTORE_PASS)); + map.put("edc.keystore.password", KEYSTORE_PASS); + return map; + } + + /** + * Loads the EDR cache. + */ + private void seedEdrCache() { + var edrCache = CONSUMER.getContext().getService(EndpointDataReferenceCache.class); + createEntries().forEach(e->edrCache.save(e.getEdrEntry(), e.getEdr())); + } + + + + +} diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/EdrCacheSetup.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/EdrCacheSetup.java new file mode 100644 index 000000000..bcaeaf68a --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/EdrCacheSetup.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.e2e; + +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.eclipse.tractusx.edc.edr.core.defaults.PersistentCacheEntry; +import org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry; + +import java.util.ArrayList; +import java.util.List; + +/** + * Creates test EDR cache entries. + */ +public class EdrCacheSetup { + + public static final String AUTHENTICATION = "authentication"; + public static final String ENDPOINT = "http://test.com"; + + public static List createEntries() { + var list = new ArrayList(); + + var edrEntry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId("79f13b89-59a6-4278-8c8e-8540849dbab8") + .agreementId("a62d02a3-eea5-4852-86d4-5482db4dffe8") + .transferProcessId("5355d524-2616-43df-9096-558afffff659") + .build(); + var edr = EndpointDataReference.Builder.newInstance() + .id("c470e649-5454-4e4d-b065-782752e5d759") + .endpoint(ENDPOINT) + .authKey(AUTHENTICATION) + .authCode(generateAuthCode()) + .build(); + list.add(new PersistentCacheEntry(edrEntry, edr)); + + var edrEntry2 = EndpointDataReferenceEntry.Builder.newInstance() + .assetId("9260f395-3d94-4b8b-bdaa-941ead596ce5") + .agreementId("d6f73f25-b0aa-4b62-843f-7cfaba532b5b8") + .transferProcessId("b2859c0a-1a4f-4d10-a3fd-9652d7b3469a") + .build(); + var edr2 = EndpointDataReference.Builder.newInstance() + .id("514a4142-3d2a-4936-97c3-7892961c6a58") + .endpoint(ENDPOINT) + .authKey(AUTHENTICATION) + .authCode(generateAuthCode()) + .build(); + list.add(new PersistentCacheEntry(edrEntry2, edr2)); + + var edrEntry3 = EndpointDataReferenceEntry.Builder.newInstance() + .assetId("9260f395-3d94-4b8b-bdaa-941ead596ce5") + .agreementId("7a23333b-03b5-4547-822b-595a54ad6d38") + .transferProcessId("7a23333b-03b5-4547-822b-595a54ad6d38") + .build(); + var edr3 = EndpointDataReference.Builder.newInstance() + .id("3563c5a1-685d-40e5-a380-0b5761523d2d") + .endpoint(ENDPOINT) + .authKey(AUTHENTICATION) + .authCode(generateAuthCode()) + .build(); + list.add(new PersistentCacheEntry(edrEntry3, edr3)); + + + return list; + } + + private static String generateAuthCode() { + //noinspection StringBufferReplaceableByString + return new StringBuilder() + .append("eyJhbGciOiJSUzI1NiIsInZlcn") + .append("Npb24iOnRydWV9.") + .append("eyJpc3MiOiJ0ZXN0LWNvb") + .append("m5lY3RvciIsInN1YiI6ImNvbnN1bW") + .append("VyLWNvbm5lY3RvciIsImF1ZCI6InRlc3Q") + .append("tY29ubmVjdG9yIiwi") + .append("aWF0IjoxNjgxOTEzN") + .append("jM2LCJleHAiOjMzNDU5NzQwNzg4LCJjaWQiOiIzMmE2M") + .append("2E3ZC04MGQ2LTRmMmUtOTBlN") + .append("i04MGJhZjVmYzJiM2MifQ.QAuotoRxpEqfuzkTcTq2w5Tcyy") + .append("3Rc3UzUjjvNc_zwgNROGLe-wO") + .append("9tFET1dJ_I5BttRxkngDS37dS4R6lN5YXaGHgcH2rf_FuVcJUS") + .append("FqTp_usGAcx6m7pQQwqpNdcYgmq0NJp3xP87EFP") + .append("HAy4kBxB5bqpmx4J-zrj9U_gerZ2WlRqpu0SdgP0S5v5D1Gm-v") + .append("YkLqgvsugrAWH3Ti7OjC5UMdj0kDFwro2NpMY8SSNryiVvBEv8hn0KZdhhebIqPd") + .append("hqbEQZ9d8WKzcgoqQ3DBd4ijzkd3Fz7ADD2gy_Hxn8Hi2LcItuB514TjCxYA") + .append("ncTNqZC_JSFEyuxwcGFVz3LdSXgw") + .toString(); + } +} + diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/KeyStoreSetup.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/KeyStoreSetup.java new file mode 100644 index 000000000..b2d6da193 --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/KeyStoreSetup.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.e2e; + +import java.io.File; +import java.io.FileOutputStream; +import java.security.KeyStore; + +/** + * Sets up a test keystore. + */ +public class KeyStoreSetup { + + public static String createKeyStore(String password) { + try { + var ks = KeyStore.getInstance(KeyStore.getDefaultType()); + + ks.load(null, password.toCharArray()); + + var file = File.createTempFile("test", "-keystore.jks"); + try (var fos = new FileOutputStream(file)) { + ks.store(fos, password.toCharArray()); + } + file.deleteOnExit(); + return file.getAbsolutePath(); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private KeyStoreSetup() { + } +} diff --git a/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java new file mode 100644 index 000000000..8bbae5f09 --- /dev/null +++ b/edc-tests/edc-dataplane-proxy-e2e/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/e2e/VaultSetup.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.dataplane.proxy.e2e; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Generates a test vault implementation. + */ +public class VaultSetup { + private static final String DELIMITER = "-----"; + private static final String HEADER = DELIMITER + "BEGIN" + " PUBLIC " + "KEY" + DELIMITER; + private static final String FOOTER = DELIMITER + "END" + " PUBLIC " + "KEY" + DELIMITER; + + private static final String VAULT_CONTENTS = "tx.dpf.data.proxy.public.key=" + HEADER + "\\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyMkG7DSIhMjFOtqQJsr+\\nKtzfKKGGQ/7mBdjwDCEj0ijKLG/LiEYWsbPA8L/oMAIdR4xpLGaajtz6wj7NbMiA\\nrtHF1HA3mNoeKGix7SfobfQ9J7gJJmSE5DA4BxatL4sPMfoV2SJanJQQjOEAA6/i\\nI+o8SeeBc/2YE55O3yeFjdHK5JIwDi9vIkGnDRBd9poyrHYV+7dcyBB45r6BwvoW\\nG41mezzlKbOl0ZtPW1T9fqp+lOiZWIHMY5ml1daGSbTWwfJxc7XfHHa8KCNQcsPR\\nhWYx6PnxvgqQwYPjvqZF7OYAMUOQX8pg6jfYiU4HgUI1jwwGw3UpJq4b3kzD3u4T\\nDQIDAQAB\\n" + FOOTER + "\n"; + + public static String createVaultStore() { + try { + var file = File.createTempFile("test", "-vault.properties"); + try (var writer = new FileWriter(file)) { + writer.write(VAULT_CONTENTS); + } + file.deleteOnExit(); + return file.getAbsolutePath(); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private VaultSetup() { + } +} diff --git a/edc-tests/runtime/build.gradle.kts b/edc-tests/runtime/build.gradle.kts index 0123162fb..7c476b287 100644 --- a/edc-tests/runtime/build.gradle.kts +++ b/edc-tests/runtime/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { exclude("org.eclipse.edc", "oauth2-core") exclude("org.eclipse.edc", "oauth2-daps") exclude(module = "data-encryption") - exclude(module = "control-plane-adapter") } // use basic (all in-mem) data plane @@ -34,7 +33,7 @@ dependencies { exclude("org.eclipse.edc", "api-observability") } - implementation(edc.core.controlplane) + implementation(libs.edc.core.controlplane) // for the controller implementation(libs.jakarta.rsApi) } diff --git a/gradle.properties b/gradle.properties index 6f20531b8..d49e094cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,11 @@ groupId=org.eclipse.tractusx.edc -version=0.3.4 +version=0.4.0 javaVersion=11 - # configure the build: # 0.0.1-SNAPSHOT is needed to leverage gradle 8 -annotationProcessorVersion=0.0.1-SNAPSHOT -edcGradlePluginsVersion=0.0.1-SNAPSHOT -metaModelVersion=0.0.1-SNAPSHOT +annotationProcessorVersion=0.0.1-milestone-9 +edcGradlePluginsVersion=0.0.1-milestone-9 +metaModelVersion=0.0.1-milestone-9 txScmConnection=scm:git:git@github.com:eclipse-tractusx/tractusx-edc.git txWebsiteUrl=https://github.com/eclipse-tractusx/tractusx-edc.git txScmUrl=https://github.com/eclipse-tractusx/tractusx-edc.git \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..34999e204 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,132 @@ +[metadata] +format.version = "1.1" + +[versions] +edc = "0.0.1-milestone-9" +postgres = "42.6.0" +awaitility = "4.2.0" +nimbus = "9.25" +azure-identity = "+" +slf4j = "2.0.7" +okhttp = "4.10.0" +mockwebserver = "5.0.0-alpha.11" +bouncyCastle-jdk18on = "1.73" +mockito = "5.2.0" +restAssured = "4.5.0" +apache-sshd = "2.9.2" +testcontainers = "1.17.6" +aws = "2.20.50" +rsApi = "3.1.0" + +[libraries] +edc-spi-catalog = { module = "org.eclipse.edc:catalog-spi", version.ref = "edc" } +edc-spi-auth = { module = "org.eclipse.edc:auth-spi", version.ref = "edc" } +edc-spi-transfer = { module = "org.eclipse.edc:transfer-spi", version.ref = "edc" } +edc-spi-core = { module = "org.eclipse.edc:core-spi", version.ref = "edc" } +edc-spi-policy = { module = "org.eclipse.edc:policy-spi", version.ref = "edc" } +edc-spi-contract = { module = "org.eclipse.edc:contract-spi", version.ref = "edc" } +edc-spi-policyengine = { module = "org.eclipse.edc:policy-engine-spi", version.ref = "edc" } +edc-spi-transaction-datasource = { module = "org.eclipse.edc:transaction-datasource-spi", version.ref = "edc" } +edc-spi-transactionspi = { module = "org.eclipse.edc:transaction-spi", version.ref = "edc" } +edc-spi-aggregateservices = { module = "org.eclipse.edc:aggregate-service-spi", version.ref = "edc" } +edc-spi-controlplane = { module = "org.eclipse.edc:control-plane-spi", version.ref = "edc" } +edc-spi-web = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } +edc-spi-http = { module = "org.eclipse.edc:http-spi", version.ref = "edc" } +edc-spi-jwt = { module = "org.eclipse.edc:jwt-spi", version.ref = "edc" } +edc-jwt-core = { module = "org.eclipse.edc:jwt-core", version.ref = "edc" } +edc-spi-oauth2 = { module = "org.eclipse.edc:oauth2-spi", version.ref = "edc" } +edc-util = { module = "org.eclipse.edc:util", version.ref = "edc" } +edc-boot = { module = "org.eclipse.edc:boot", version.ref = "edc" } +edc-config-filesystem = { module = "org.eclipse.edc:configuration-filesystem", version.ref = "edc" } +edc-vault-filesystem = { module = "org.eclipse.edc:vault-filesystem", version.ref = "edc" } +edc-core-controlplane = { module = "org.eclipse.edc:control-plane-core", version.ref = "edc" } +edc-core-connector = { module = "org.eclipse.edc:connector-core", version.ref = "edc" } +edc-core-jetty = { module = "org.eclipse.edc:jetty-core", version.ref = "edc" } +edc-core-jersey = { module = "org.eclipse.edc:jersey-core", version.ref = "edc" } +edc-core-api = { module = "org.eclipse.edc:api-core", version.ref = "edc" } +edc-junit = { module = "org.eclipse.edc:junit", version.ref = "edc" } +edc-api-management-config = { module = "org.eclipse.edc:management-api-configuration", version.ref = "edc" } +edc-api-management = { module = "org.eclipse.edc:management-api", version.ref = "edc" } +edc-api-catalog = { module = "org.eclipse.edc:catalog-api", version.ref = "edc" } +edc-api-observability = { module = "org.eclipse.edc:api-observability", version.ref = "edc" } +edc-api-contractnegotiation = { module = "org.eclipse.edc:contract-negotiation-api", version.ref = "edc" } +edc-api-dataplane = { module = "org.eclipse.edc:dataplane-api", version.ref = "edc" } +edc-api-transferprocess = { module = "org.eclipse.edc:transfer-process-api", version.ref = "edc" } +edc-dsp = { module = "org.eclipse.edc:dsp", version.ref = "edc" } +edc-iam-mock = { module = "org.eclipse.edc:iam-mock", version.ref = "edc" } +edc-policy-engine = { module = "org.eclipse.edc:policy-engine", version.ref = "edc" } +edc-auth-tokenbased = { module = "org.eclipse.edc:auth-tokenbased", version.ref = "edc" } +edc-auth-oauth2-core = { module = "org.eclipse.edc:oauth2-core", version.ref = "edc" } +edc-auth-oauth2-daps = { module = "org.eclipse.edc:oauth2-core", version.ref = "edc" } +edc-transaction-local = { module = "org.eclipse.edc:transaction-local", version.ref = "edc" } +edc-ext-http = { module = "org.eclipse.edc:http", version.ref = "edc" } +edc-ext-azure-cosmos-core = { module = "org.eclipse.edc:azure-cosmos-core", version.ref = "edc" } +edc-ext-azure-test = { module = "org.eclipse.edc:azure-test", version.ref = "edc" } +edc-ext-jsonld = { module = "org.eclipse.edc:json-ld", version.ref = "edc" } + +# implementations +edc-sql-assetindex = { module = "org.eclipse.edc:asset-index-sql", version.ref = "edc" } +edc-sql-contract-definition = { module = "org.eclipse.edc:contract-definition-store-sql", version.ref = "edc" } +edc-sql-contract-negotiation = { module = "org.eclipse.edc:contract-negotiation-store-sql", version.ref = "edc" } +edc-sql-transferprocess = { module = "org.eclipse.edc:transfer-process-store-sql", version.ref = "edc" } +edc-sql-policydef = { module = "org.eclipse.edc:policy-definition-store-sql", version.ref = "edc" } +edc-sql-core = { module = "org.eclipse.edc:sql-core", version.ref = "edc" } +edc-sql-lease = { module = "org.eclipse.edc:sql-lease", version.ref = "edc" } +edc-sql-pool = { module = "org.eclipse.edc:sql-pool-apache-commons", version.ref = "edc" } + +# azure stuff +edc-azure-vault = { module = "org.eclipse.edc:vault-azure", version.ref = "edc" } +edc-azure-identity = { module = "com.azure:azure-identity", version.ref = "azure-identity" } + +# Control Plane implementations +edc-controlplane-callback-dispatcher-event = { module = "org.eclipse.edc:callback-event-dispatcher", version.ref = "edc" } +edc-controlplane-callback-dispatcher-http = { module = "org.eclipse.edc:callback-http-dispatcher", version.ref = "edc" } + + +# DPF modules +edc-spi-dataplane-dataplane = { module = "org.eclipse.edc:data-plane-spi", version.ref = "edc" } +edc-spi-dataplane-transfer = { module = "org.eclipse.edc:transfer-data-plane-spi", version.ref = "edc" } +edc-spi-dataplane-selector = { module = "org.eclipse.edc:data-plane-selector-spi", version.ref = "edc" } +edc-dpf-transferclient = { module = "org.eclipse.edc:data-plane-transfer-client", version.ref = "edc" } +edc-dpf-selector-client = { module = "org.eclipse.edc:data-plane-selector-client", version.ref = "edc" } +edc-dpf-selector-spi = { module = "org.eclipse.edc:data-plane-selector-spi", version.ref = "edc" } +edc-dpf-selector-core = { module = "org.eclipse.edc:data-plane-selector-core", version.ref = "edc" } +edc-dpf-transfer = { module = "org.eclipse.edc:transfer-data-plane", version.ref = "edc" } +edc-dpf-framework = { module = "org.eclipse.edc:data-plane-framework", version.ref = "edc" } +edc-dpf-core = { module = "org.eclipse.edc:data-plane-core", version.ref = "edc" } +edc-dpf-util = { module = "org.eclipse.edc:data-plane-util", version.ref = "edc" } +edc-dpf-awss3 = { module = "org.eclipse.edc:data-plane-aws-s3", version.ref = "edc" } +edc-dpf-http = { module = "org.eclipse.edc:data-plane-http", version.ref = "edc" } +edc-dpf-oauth2 = { module = "org.eclipse.edc:data-plane-http-oauth2", version.ref = "edc" } +edc-dpf-api = { module = "org.eclipse.edc:data-plane-api", version.ref = "edc" } + +# micrometer and other infra stuff +edc-micrometer-core = { module = "org.eclipse.edc:micrometer-core", version.ref = "edc" } +edc-micrometer-jersey = { module = "org.eclipse.edc:jersey-micrometer", version.ref = "edc" } +edc-micrometer-jetty = { module = "org.eclipse.edc:jetty-micrometer", version.ref = "edc" } +edc-monitor-jdklogger = { module = "org.eclipse.edc:monitor-jdk-logger", version.ref = "edc" } +edc-transfer-dynamicreceiver = { module = "org.eclipse.edc:transfer-pull-http-dynamic-receiver", version.ref = "edc" } +edc-transfer-receiver = { module = "org.eclipse.edc:transfer-pull-http-receiver", version.ref = "edc" } + +# other deps + +postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } +awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +nimbus-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" } +bouncyCastle-bcpkixJdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncyCastle-jdk18on" } +mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" } +restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" } +apache-sshd-core = { module = "org.apache.sshd:sshd-core", version.ref = "apache-sshd" } +apache-sshd-sftp = { module = "org.apache.sshd:sshd-sftp", version.ref = "apache-sshd" } +testcontainers-junit = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" } +aws-s3 = { module = "software.amazon.awssdk:s3", version.ref = "aws" } +jakarta-rsApi = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version.ref = "rsApi" } + +[bundles] +edc-connector = ["edc.boot", "edc.core-connector", "edc.core-controlplane", "edc.api-observability"] +edc-dpf = ["edc.dpf-transfer", "edc.dpf-selector-core", "edc.dpf-selector-client", "edc.spi-dataplane-selector"] +edc-sqlstores = ["edc.sql-assetindex", "edc.sql-contract-definition", "edc.sql-contract-negotiation", "edc.sql-transferprocess", "edc.sql-policydef"] +edc-monitoring = ["edc.micrometer-core", "edc.micrometer-jersey", "edc.micrometer-jetty"] diff --git a/resources/openapi/yaml/control-plane-adapter-api.yaml b/resources/openapi/yaml/control-plane-adapter-api.yaml new file mode 100644 index 000000000..de9f7fbb0 --- /dev/null +++ b/resources/openapi/yaml/control-plane-adapter-api.yaml @@ -0,0 +1,324 @@ +openapi: 3.0.1 +paths: + /adapter/edrs: + post: + description: Initiates an EDR negotiation by handling a contract negotiation + first and then a transfer process for a given offer and with the given counter + part. Please note that successfully invoking this endpoint only means that + the negotiation was initiated. + operationId: initiateEdrNegotiation + requestBody: + content: + application/json: + schema: + type: object + additionalProperties: + $ref: '#/components/schemas/NegotiateEdrRequestDto' + example: null + properties: + empty: + type: boolean + example: null + valueType: + type: string + enum: + - ARRAY + - OBJECT + - STRING + - NUMBER + - "TRUE" + - "FALSE" + - "NULL" + example: null + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/IdResponseDto' + description: The negotiation was successfully initiated. + "400": + content: + application/json: + schema: + type: array + example: null + items: + $ref: '#/components/schemas/ApiErrorDetail' + description: Request body was malformed + tags: + - Control Plane Adapter EDR Api +components: + schemas: + Action: + type: object + example: null + properties: + constraint: + $ref: '#/components/schemas/Constraint' + includedIn: + type: string + example: null + type: + type: string + example: null + ApiErrorDetail: + type: object + example: null + properties: + invalidValue: + type: string + example: null + message: + type: string + example: null + path: + type: string + example: null + type: + type: string + example: null + CallbackAddressDto: + type: object + example: null + properties: + authCodeId: + type: string + example: null + authKey: + type: string + example: null + events: + type: array + example: null + items: + type: string + example: null + uniqueItems: true + transactional: + type: boolean + example: null + uri: + type: string + example: null + required: + - events + - uri + Constraint: + type: object + discriminator: + propertyName: edctype + example: null + properties: + edctype: + type: string + example: null + required: + - edctype + ContractOfferDescription: + type: object + example: null + properties: + assetId: + type: string + example: null + offerId: + type: string + example: null + policy: + $ref: '#/components/schemas/Policy' + validity: + type: integer + format: int64 + example: null + required: + - assetId + - offerId + - policy + Duty: + type: object + example: null + properties: + action: + $ref: '#/components/schemas/Action' + assignee: + type: string + example: null + assigner: + type: string + example: null + consequence: + $ref: '#/components/schemas/Duty' + constraints: + type: array + example: null + items: + $ref: '#/components/schemas/Constraint' + parentPermission: + $ref: '#/components/schemas/Permission' + target: + type: string + example: null + IdResponseDto: + type: object + example: null + properties: + createdAt: + type: integer + format: int64 + example: null + id: + type: string + example: null + JsonObject: + type: object + additionalProperties: + $ref: '#/components/schemas/NegotiateEdrRequestDto' + example: null + properties: + empty: + type: boolean + example: null + valueType: + type: string + enum: + - ARRAY + - OBJECT + - STRING + - NUMBER + - "TRUE" + - "FALSE" + - "NULL" + example: null + JsonValue: + type: object + example: null + properties: + valueType: + type: string + enum: + - ARRAY + - OBJECT + - STRING + - NUMBER + - "TRUE" + - "FALSE" + - "NULL" + example: null + NegotiateEdrRequestDto: + type: object + example: null + properties: + callbackAddresses: + type: array + example: null + items: + $ref: '#/components/schemas/CallbackAddressDto' + connectorAddress: + type: string + example: null + connectorId: + type: string + example: null + offer: + $ref: '#/components/schemas/ContractOfferDescription' + protocol: + type: string + example: null + providerId: + type: string + example: null + required: + - connectorAddress + - connectorId + - offer + - protocol + Permission: + type: object + example: null + properties: + action: + $ref: '#/components/schemas/Action' + assignee: + type: string + example: null + assigner: + type: string + example: null + constraints: + type: array + example: null + items: + $ref: '#/components/schemas/Constraint' + duties: + type: array + example: null + items: + $ref: '#/components/schemas/Duty' + target: + type: string + example: null + Policy: + type: object + example: null + properties: + '@type': + type: string + enum: + - SET + - OFFER + - CONTRACT + example: null + assignee: + type: string + example: null + assigner: + type: string + example: null + extensibleProperties: + type: object + additionalProperties: + type: object + example: null + example: null + inheritsFrom: + type: string + example: null + obligations: + type: array + example: null + items: + $ref: '#/components/schemas/Duty' + permissions: + type: array + example: null + items: + $ref: '#/components/schemas/Permission' + prohibitions: + type: array + example: null + items: + $ref: '#/components/schemas/Prohibition' + target: + type: string + example: null + Prohibition: + type: object + example: null + properties: + action: + $ref: '#/components/schemas/Action' + assignee: + type: string + example: null + assigner: + type: string + example: null + constraints: + type: array + example: null + items: + $ref: '#/components/schemas/Constraint' + target: + type: string + example: null diff --git a/resources/openapi/yaml/observability-api-customization.yaml b/resources/openapi/yaml/observability-api-customization.yaml new file mode 100644 index 000000000..063a3122a --- /dev/null +++ b/resources/openapi/yaml/observability-api-customization.yaml @@ -0,0 +1,105 @@ +openapi: 3.0.1 +paths: + /check/health: + get: + description: Performs a health check to determine whether the runtime is working + properly. + operationId: checkHealth + responses: + "200": + content: + application/json: + schema: + type: array + example: null + items: + $ref: '#/components/schemas/HealthStatus' + tags: + - Application Observability + /check/liveness: + get: + description: Performs a liveness probe to determine whether the runtime is working + properly. + operationId: getLiveness + responses: + "200": + content: + application/json: + schema: + type: array + example: null + items: + $ref: '#/components/schemas/HealthStatus' + tags: + - Application Observability + /check/readiness: + get: + description: Performs a readiness probe to determine whether the runtime is + able to accept requests. + operationId: getReadiness + responses: + "200": + content: + application/json: + schema: + type: array + example: null + items: + $ref: '#/components/schemas/HealthStatus' + tags: + - Application Observability + /check/startup: + get: + description: Performs a startup probe to determine whether the runtime has completed + startup. + operationId: getStartup + responses: + "200": + content: + application/json: + schema: + type: array + example: null + items: + $ref: '#/components/schemas/HealthStatus' + tags: + - Application Observability +components: + schemas: + Failure: + type: object + example: null + properties: + failureDetail: + type: string + example: null + messages: + type: array + example: null + items: + type: string + example: null + HealthCheckResult: + type: object + example: null + properties: + component: + type: string + example: null + failure: + $ref: '#/components/schemas/Failure' + isHealthy: + type: boolean + example: null + HealthStatus: + type: object + example: null + properties: + componentResults: + type: array + example: null + items: + $ref: '#/components/schemas/HealthCheckResult' + isSystemHealthy: + type: boolean + example: null diff --git a/settings.gradle.kts b/settings.gradle.kts index 4a269e4e0..ada149481 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,8 +19,15 @@ rootProject.name = "tractusx-edc" +// spi modules +include(":spi:control-plane-adapter-spi") +include(":spi:edr-cache-spi") + +// core modules +include(":core:edr-cache-core") + + include(":edc-extensions:business-partner-validation") -include(":edc-extensions:control-plane-adapter") include(":edc-extensions:cx-oauth2") include(":edc-extensions:data-encryption") include(":edc-extensions:dataplane-selector-configuration") @@ -31,6 +38,10 @@ include(":edc-extensions:observability-api-customization") include(":edc-extensions:transferprocess-sftp-client") include(":edc-extensions:transferprocess-sftp-common") include(":edc-extensions:transferprocess-sftp-provisioner") +include(":edc-extensions:control-plane-adapter-api") +include(":edc-extensions:control-plane-adapter-callback") + + include(":edc-tests:e2e-tests") include(":edc-tests:runtime") include(":edc-tests:cucumber") @@ -48,6 +59,12 @@ include(":edc-dataplane") include(":edc-dataplane:edc-dataplane-azure-vault") include(":edc-dataplane:edc-dataplane-base") include(":edc-dataplane:edc-dataplane-hashicorp-vault") +include(":edc-dataplane:edc-dataplane-proxy-consumer-api") +include(":edc-dataplane:edc-dataplane-proxy-provider-spi") +include(":edc-dataplane:edc-dataplane-proxy-provider-core") +include(":edc-dataplane:edc-dataplane-proxy-provider-api") +include(":edc-tests:edc-dataplane-proxy-e2e") + // this is needed to have access to snapshot builds of plugins pluginManagement { @@ -68,128 +85,4 @@ dependencyResolutionManagement { mavenCentral() mavenLocal() } - versionCatalogs { - create("libs") { - from("org.eclipse.edc:edc-versions:0.0.1-20230220-SNAPSHOT") - library("testcontainers-junit", "org.testcontainers", "junit-jupiter").version("1.17.6") - library("apache-sshd-core", "org.apache.sshd", "sshd-core").version("2.9.2") - library("apache-sshd-sftp", "org.apache.sshd", "sshd-sftp").version("2.9.2") - } - // create version catalog for all EDC modules - create("edc") { - version("edc", "0.0.1-20230220.patch1") - library("spi-catalog", "org.eclipse.edc", "catalog-spi").versionRef("edc") - library("spi-auth", "org.eclipse.edc", "auth-spi").versionRef("edc") - library("spi-transfer", "org.eclipse.edc", "transfer-spi").versionRef("edc") - library("spi-core", "org.eclipse.edc", "core-spi").versionRef("edc") - library("spi-policy", "org.eclipse.edc", "policy-spi").versionRef("edc") - library("spi-contract", "org.eclipse.edc", "contract-spi").versionRef("edc") - library("spi-policyengine", "org.eclipse.edc", "policy-engine-spi").versionRef("edc") - library("spi-transaction-datasource", "org.eclipse.edc", "transaction-datasource-spi").versionRef("edc") - library("spi-transactionspi", "org.eclipse.edc", "transaction-spi").versionRef("edc") - library("spi-aggregateservices", "org.eclipse.edc", "aggregate-service-spi").versionRef("edc") - library("spi-web", "org.eclipse.edc", "web-spi").versionRef("edc") - library("spi-jwt", "org.eclipse.edc", "jwt-spi").versionRef("edc") - library("spi-oauth2", "org.eclipse.edc", "oauth2-spi").versionRef("edc") - library("util", "org.eclipse.edc", "util").versionRef("edc") - library("boot", "org.eclipse.edc", "boot").versionRef("edc") - library("config-filesystem", "org.eclipse.edc", "configuration-filesystem").versionRef("edc") - library("vault-filesystem", "org.eclipse.edc", "vault-filesystem").versionRef("edc") - library("core-controlplane", "org.eclipse.edc", "control-plane-core").versionRef("edc") - library("core-connector", "org.eclipse.edc", "connector-core").versionRef("edc") - library("core-jetty", "org.eclipse.edc", "jetty-core").versionRef("edc") - library("core-jersey", "org.eclipse.edc", "jersey-core").versionRef("edc") - library("core-api", "org.eclipse.edc", "api-core").versionRef("edc") - library("junit", "org.eclipse.edc", "junit").versionRef("edc") - library("api-management-config", "org.eclipse.edc", "management-api-configuration").versionRef("edc") - library("api-management", "org.eclipse.edc", "management-api").versionRef("edc") - library("api-catalog", "org.eclipse.edc", "catalog-api").versionRef("edc") - library("api-observability", "org.eclipse.edc", "api-observability").versionRef("edc") - library("api-contractnegotiation", "org.eclipse.edc", "contract-negotiation-api").versionRef("edc") - library("api-dataplane", "org.eclipse.edc", "data-plane-api").versionRef("edc") - library("api-transferprocess", "org.eclipse.edc", "transfer-process-api").versionRef("edc") - library("ext-http", "org.eclipse.edc", "http").versionRef("edc") - library("spi-ids", "org.eclipse.edc", "ids-spi").versionRef("edc") - library("ids", "org.eclipse.edc", "ids").versionRef("edc") - library("iam-mock", "org.eclipse.edc", "iam-mock").versionRef("edc") - library("ext-azure-cosmos-core", "org.eclipse.edc", "azure-cosmos-core").versionRef("edc") - library("ext-azure-test", "org.eclipse.edc", "azure-test").versionRef("edc") - library("policy-engine", "org.eclipse.edc", "policy-engine").versionRef("edc") - library("auth-tokenbased", "org.eclipse.edc", "auth-tokenbased").versionRef("edc") - library("auth-oauth2-core", "org.eclipse.edc", "oauth2-core").versionRef("edc") - library("auth-oauth2-daps", "org.eclipse.edc", "oauth2-daps").versionRef("edc") - library("transaction-local", "org.eclipse.edc", "transaction-local").versionRef("edc") - - // implementations - library("sql-assetindex", "org.eclipse.edc", "asset-index-sql").versionRef("edc") - library("sql-contract-definition", "org.eclipse.edc", "contract-definition-store-sql").versionRef("edc") - library("sql-contract-negotiation", "org.eclipse.edc", "contract-negotiation-store-sql").versionRef("edc") - library("sql-transferprocess", "org.eclipse.edc", "transfer-process-store-sql").versionRef("edc") - library("sql-policydef", "org.eclipse.edc", "policy-definition-store-sql").versionRef("edc") - library("sql-core", "org.eclipse.edc", "sql-core").versionRef("edc") - library("sql-lease", "org.eclipse.edc", "sql-lease").versionRef("edc") - library("sql-pool", "org.eclipse.edc", "sql-pool-apache-commons").versionRef("edc") - - // azure stuff - library("azure-vault", "org.eclipse.edc", "vault-azure").versionRef("edc") - library("azure-identity", "com.azure:azure-identity:+") - - // DPF modules - library("spi-dataplane-dataplane", "org.eclipse.edc", "data-plane-spi").versionRef("edc") - library("spi-dataplane-transfer", "org.eclipse.edc", "transfer-data-plane-spi").versionRef("edc") - library("spi-dataplane-selector", "org.eclipse.edc", "data-plane-selector-spi").versionRef("edc") - library("dpf-transferclient", "org.eclipse.edc", "data-plane-transfer-client").versionRef("edc") - library("dpf-selector-client", "org.eclipse.edc", "data-plane-selector-client").versionRef("edc") - library("dpf-selector-spi", "org.eclipse.edc", "data-plane-selector-spi").versionRef("edc") - library("dpf-selector-core", "org.eclipse.edc", "data-plane-selector-core").versionRef("edc") - library("dpf-transfer", "org.eclipse.edc", "transfer-data-plane").versionRef("edc") - library("dpf-framework", "org.eclipse.edc", "data-plane-framework").versionRef("edc") - library("dpf-core", "org.eclipse.edc", "data-plane-core").versionRef("edc") - library("dpf-util", "org.eclipse.edc", "data-plane-util").versionRef("edc") - library("dpf-awss3", "org.eclipse.edc", "data-plane-aws-s3").versionRef("edc") - library("dpf-http", "org.eclipse.edc", "data-plane-http").versionRef("edc") - library("dpf-oauth2", "org.eclipse.edc", "data-plane-http-oauth2").versionRef("edc") - library("dpf-api", "org.eclipse.edc", "data-plane-api").versionRef("edc") - - // micrometer and other infra stuff - library("micrometer-core", "org.eclipse.edc", "micrometer-core").versionRef("edc") - library("micrometer-jersey", "org.eclipse.edc", "jersey-micrometer").versionRef("edc") - library("micrometer-jetty", "org.eclipse.edc", "jetty-micrometer").versionRef("edc") - library("monitor-jdklogger", "org.eclipse.edc", "monitor-jdk-logger").versionRef("edc") - library( - "transfer.dynamicreceiver", - "org.eclipse.edc", - "transfer-pull-http-dynamic-receiver" - ).versionRef("edc") - - library("transfer.receiver", "org.eclipse.edc", "transfer-pull-http-receiver").versionRef("edc") - - bundle( - "connector", - listOf("boot", "core-connector", "core-jersey", "core-controlplane", "api-observability") - ) - - bundle( - "dpf", - listOf("dpf-transfer", "dpf-selector-core", "dpf-selector-client", "spi-dataplane-selector") - ) - - bundle( - "sqlstores", - listOf( - "sql-assetindex", - "sql-contract-definition", - "sql-contract-negotiation", - "sql-transferprocess", - "sql-policydef" - ) - ) - - bundle( - "monitoring", - listOf("micrometer-core", "micrometer-jersey", "micrometer-jetty") -// listOf("micrometer-core", "micrometer-jersey", "micrometer-jetty", "monitor-jdklogger") - ) - } - } } diff --git a/spi/control-plane-adapter-spi/build.gradle.kts b/spi/control-plane-adapter-spi/build.gradle.kts new file mode 100644 index 000000000..d01290419 --- /dev/null +++ b/spi/control-plane-adapter-spi/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` + `maven-publish` +} + + +dependencies { + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.contract) + implementation(libs.edc.spi.aggregateservices) + implementation(libs.edc.spi.controlplane) +} diff --git a/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallback.java b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallback.java new file mode 100644 index 000000000..12a6901fa --- /dev/null +++ b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.spi.cp.adapter.callback; + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; + +/** + * In process callback for handling {@link CallbackEventRemoteMessage} + */ +@FunctionalInterface +public interface InProcessCallback { + + Result invoke(CallbackEventRemoteMessage message); +} diff --git a/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallbackRegistry.java b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallbackRegistry.java new file mode 100644 index 000000000..2968530ae --- /dev/null +++ b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/callback/InProcessCallbackRegistry.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.spi.cp.adapter.callback; + + +import org.eclipse.edc.connector.spi.callback.CallbackEventRemoteMessage; +import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.result.Result; + + +/** + * Registry for {@link InProcessCallback} + */ +@ExtensionPoint +public interface InProcessCallbackRegistry { + + /** + * Register an {@link InProcessCallback} + * + * @param callback The callback + */ + void registerHandler(InProcessCallback callback); + + /** + * Handles a {@link CallbackEventRemoteMessage} by calling registered {@link InProcessCallback} + * + * @param message The message + */ + Result handleMessage(CallbackEventRemoteMessage message); +} diff --git a/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/model/NegotiateEdrRequest.java b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/model/NegotiateEdrRequest.java new file mode 100644 index 000000000..846c16653 --- /dev/null +++ b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/model/NegotiateEdrRequest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.spi.cp.adapter.model; + +import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class NegotiateEdrRequest { + + private String connectorAddress; + private String protocol = "ids-multipart"; + private String connectorId; + private ContractOffer offer; + + private List callbackAddresses = new ArrayList<>(); + + private NegotiateEdrRequest() { + + } + + public String getConnectorAddress() { + return connectorAddress; + } + + public String getProtocol() { + return protocol; + } + + public String getConnectorId() { + return connectorId; + } + + + public List getCallbackAddresses() { + return callbackAddresses; + } + + public ContractOffer getOffer() { + return offer; + } + + + public static final class Builder { + private final NegotiateEdrRequest entity; + + private Builder() { + entity = new NegotiateEdrRequest(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder connectorAddress(String connectorAddress) { + entity.connectorAddress = connectorAddress; + return this; + } + + public Builder protocol(String protocol) { + entity.protocol = protocol; + return this; + } + + public Builder connectorId(String connectorId) { + entity.connectorId = connectorId; + return this; + } + + public Builder offer(ContractOffer offer) { + entity.offer = offer; + return this; + } + + public Builder callbackAddresses(List callbackAddresses) { + entity.callbackAddresses = callbackAddresses; + return this; + } + + public NegotiateEdrRequest build() { + Objects.requireNonNull(entity.protocol, "protocol should not be null"); + Objects.requireNonNull(entity.connectorAddress, "connector address should not be null"); + Objects.requireNonNull(entity.offer, "offer should not be null"); + return entity; + } + } +} diff --git a/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/service/AdapterTransferProcessService.java b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/service/AdapterTransferProcessService.java new file mode 100644 index 000000000..2ab135030 --- /dev/null +++ b/spi/control-plane-adapter-spi/src/main/java/org/eclipse/tractusx/edc/spi/cp/adapter/service/AdapterTransferProcessService.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.spi.cp.adapter.service; + +import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.service.spi.result.ServiceResult; +import org.eclipse.tractusx.edc.spi.cp.adapter.model.NegotiateEdrRequest; + +/** + * Service for opening a transfer process. + */ +public interface AdapterTransferProcessService { + + /** + * Open a transfer process by firing a contract negotiation. Implementors should fire a contract negotiation + * and automatically fire a transfer process once the agreement has been reached. + * + * @param request The open request + * @return The result containing the contract negotiation id + */ + ServiceResult initiateEdrNegotiation(NegotiateEdrRequest request); +} diff --git a/spi/edr-cache-spi/build.gradle.kts b/spi/edr-cache-spi/build.gradle.kts new file mode 100644 index 000000000..9ca2f9437 --- /dev/null +++ b/spi/edr-cache-spi/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) +} + diff --git a/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceCache.java b/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceCache.java new file mode 100644 index 000000000..67e533bf3 --- /dev/null +++ b/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.spi; + +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Caches and resolves {@link EndpointDataReference}s + */ +public interface EndpointDataReferenceCache { + + /** + * Resolves an {@link EndpointDataReference} for the transfer process, returning null if one does not exist. + */ + @Nullable + EndpointDataReference resolveReference(String transferProcessId); + + /** + * Resolves the {@link EndpointDataReference}s for the asset. + */ + @NotNull + List referencesForAsset(String assetId); + + /** + * Returns the {@link EndpointDataReferenceEntry}s for the asset. + */ + @NotNull + List entriesForAsset(String assetId); + + /** + * Saves an {@link EndpointDataReference} to the cache using upsert semantics. + */ + void save(EndpointDataReferenceEntry entry, EndpointDataReference edr); + + /** + * Deletes stored endpoint reference data associated with the given transfer process. + */ + StoreResult deleteByTransferProcessId(String id); + +} diff --git a/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntry.java b/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntry.java new file mode 100644 index 000000000..617c856cc --- /dev/null +++ b/spi/edr-cache-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntry.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.spi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; + +import static java.util.Objects.requireNonNull; + +/** + * An entry in the cache for an {@link EndpointDataReference}. + */ +@JsonDeserialize(builder = EndpointDataReferenceEntry.Builder.class) +public class EndpointDataReferenceEntry { + private String assetId; + private String agreementId; + private String transferProcessId; + + private EndpointDataReferenceEntry() { + } + + public String getAssetId() { + return assetId; + } + + public String getAgreementId() { + return agreementId; + } + + public String getTransferProcessId() { + return transferProcessId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + var that = (EndpointDataReferenceEntry) o; + + return transferProcessId.equals(that.transferProcessId); + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private final EndpointDataReferenceEntry entry; + + private Builder() { + entry = new EndpointDataReferenceEntry(); + } + + @JsonCreator + public static Builder newInstance() { + return new Builder(); + } + + public Builder assetId(String assetId) { + entry.assetId = assetId; + return this; + } + + public Builder agreementId(String agreementId) { + entry.agreementId = agreementId; + return this; + } + + public Builder transferProcessId(String transferProcessId) { + entry.transferProcessId = transferProcessId; + return this; + } + + public EndpointDataReferenceEntry build() { + requireNonNull(entry.assetId, "assetId"); + requireNonNull(entry.agreementId, "agreementId"); + requireNonNull(entry.transferProcessId, "transferProcessId"); + return entry; + } + } + +} diff --git a/spi/edr-cache-spi/src/test/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntryTest.java b/spi/edr-cache-spi/src/test/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntryTest.java new file mode 100644 index 000000000..75266e1bc --- /dev/null +++ b/spi/edr-cache-spi/src/test/java/org/eclipse/tractusx/edc/edr/spi/EndpointDataReferenceEntryTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.tractusx.edc.edr.spi; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; + +class EndpointDataReferenceEntryTest { + + @Test + void verify_serializeDeserialize() throws JsonProcessingException { + var mapper = new ObjectMapper(); + + var entry = EndpointDataReferenceEntry.Builder.newInstance() + .assetId(randomUUID().toString()) + .agreementId(randomUUID().toString()) + .transferProcessId(randomUUID().toString()) + .build(); + + var serialized = mapper.writeValueAsString(entry); + var deserialized = mapper.readValue(serialized, EndpointDataReferenceEntry.class); + + assertThat(deserialized.getTransferProcessId()).isNotEmpty(); + assertThat(deserialized.getAssetId()).isNotEmpty(); + assertThat(deserialized.getAgreementId()).isNotEmpty(); + } +}